(Homepage
|
Alle Folien als Grafiken
)
Automatische Softwaretests
Beware of bugs in the above code;
I have only proved it correct, not tried it.
-- Donald Knuth
LinuxTag 2004, Karlsruhe
Bernhard Herzog <Bernhard.Herzog@intevation.de>
Software Entwicklung
Intevation GmbH, Osnabrück
(page 1)
Ziel des Vortrags
- Automatische Tests sollen häufiger in der Freien Software
eingesetzt werden.
Zielgruppe
- Entwickler Freier Software
- Keine Erfahrung mit automatischen Tests nötig
- Erfahrung mit Python nützlich aber nicht erforderlich
(page 2)
Inhalt
- Was sind automatische Tests?
Warum sollte man sie Einsetzen?
Wie wirken sie sich auf das Projekt aus?
Wie kann man automatische Tests in bestehende Projekte einführen?
Wie geht das genau? Praxis anhand von Python
(page 3)
Software Tests
- bewährtes Mittel der Qualitätskontrolle
aber:
- manuelle Tests sind mühsam und fehlerträchtig
- Bei Freier Software kaum Ressourcen für systematische manuelle Tests vorhanden
Abhilfe: Tests automatisieren
(page 4)
Automatische Tests
- Testsuite mit allen Tests
- Einfach auszuführen
- Ergebnis leicht erkennbar
Integraler Bestandteil des Entwicklungsprozesses
- Tests nach jeder Änderung ausführen
- Tests gleichzeitig mit dem Code schreiben
Zwingen zum Nachdenken über den Code
Tests sind Dokumentation
Tests führen zu modularem, wartbarem Design
Testen ist Metaprogrammierung
(page 5)
Entwicklungsprozess
Grundregeln:
1. Nach jeder Änderung die Testsuite aufrufen
2. Zu jedem neuen Feature gehören neue Tests
3. Zu jedem neuen Bugfix gehört ein Test
(page 6)
Entwicklungsprozess
Grundregel 1:
Nach jeder Änderung die Testsuite aufrufen
- Stellt sicher, dass keine neuen Fehler auftreten
- Neue Fehler fallen sofort auf
- Neue Fehler sind leicht einzugrenzen
- Weniger Unsicherheit bei Änderungen
- Erleichtert Refactoring
(page 7)
Entwicklungsprozess
Grundregel 2:
Zu jedem neuen Feature gehören neue Tests
- Ausbau der Testsuite
- Tests zuerst schreiben
- Zwingt über den Code nachzudenken
- Der neue Code ist sofort gut testbar
- Besseres Design des neuen Codes
- Manuelle Tests meist nicht mehr erforderlich
- Vorgehen:
- Test schreiben, Code nur rudimentär
- Testsuitelauf: Test schlägt an
- Code vervollständigen
- Testsuitelauf: Alle Tests OK
(page 8)
Entwicklungsprozess
Grundregel 3:
Zu jedem neuen Bugfix gehört ein Test
- Bug = Testsuite unvollständig
- Pflege der Testsuite
- Vorgehen:
- Bug reproduzieren und analysieren
- Neuen Test schreiben
- Testsuitelauf: neuer Test schlägt an
- Bug beheben
- Testsuitelauf: alle Tests OK
- Gute Gelegenheit bisher ungetesteten Code mit Tests auszustatten
(page 9)
Design
Tests
- Jeder Test soll nur eine Sache testen
- Testfälle müssen unabhängig voneinander sein
- Leichter zu verstehen
- Können einzeln ausgeführt werden
Getesteter Code
- Muss testbar sein
- Geringe Kopplung zwischen Modulen
- Möglichst keine globalen Variablen
(page 10)
Klassifikation von Tests
Nach Granularität
- Akzeptanztest: Ganzes System
- Integrationstest: Teilkomponenten
- Unittest: Modul/Klasse/Funktion
Nach Kopplungsgrad
- Black Box Test: Nur API Spezifikation
- White Box Test: Mit Wissen über die Implementation
(page 11)
Einführung einer Testsuite
Einfach bei neuen Projekten
- Testsuite und Programm wachsen gleichzeitig
- Immer gute Abdeckung der Funktionalität
- Zu Anfang kann es sinnvoll sein sehr grobkörnige Tests zu schreiben
- Feinkörnige Tests erst, wenn das Design stabiler wird
(page 12)
Einführung einer Testsuite
Schwieriger bei bestehenden Projekten
- Hauptproblem: Wie überzeugt man die anderen Entwickler?
- Bei Teilkomponente Anfangen
- Nur noch Patches mit zugehörigen Tests akzeptieren
- Nachteil: Höhere Hürde für neue Entwickler
(page 13)
Grenzen von Tests
Program testing can be a very effective
way to show the presence of bugs,
but it is hopelessly inadequate
for showing their absence.
-- Edsger W. Dijkstra
- Alle Tests erfolgreich != Bugfreie Software
- Manche Programmteile nur schwer oder gar nicht testbar
- Manches nicht deterministisch genug für Tests
(page 14)
Schwer Testbare Komponenten
- GUI
- GUI-Schicht möglichst dünn halten
- Programmlogik von GUI Elementen lösen
- Model-View-Controller
- Datenbankanbindungen
- Trennung DB-Zugriff <-> Logik
- DB-Zugriff in Tests durch Mockobjekte ersetzen
- oder: komplette Datenbank als Teil der Testsuite aufsetzen
(page 15)
Automatische Tests mit Python
Zwei Module in der Standardbibliothek
- Unittesting Framework
- Angelehnt and JUnit
doctest
- Tested Beispielcode aus Docstrings
- Tested die Docstrings bzw. den Code
- Literate Testing
- lässt sich in
unittest einbinden
(page 16)
Beispiel
Aufgabe: Schreibe eine Funktion, die eine
Zeichenkette mit durch Leerzeichen, Tabulatoren
und anderen Zwischenräumen getrennten ganzen
Zahlen in eine Liste mit Integerobjekten
umwandelt.
"1 2 3" -> [1, 2, 3]
(page 17)
Tests mit unittest
import unittest
from parselist import parse_int_list
class TestParseIntList(unittest.TestCase):
def test_multiple(self):
"""Test parse_int_list with several ints"""
self.assertEquals(parse_int_list("1 2 3"),
[1, 2, 3])
if __name__ == "__main__":
unittest.main()
(page 18)
Rest der Methoden
def test_single(self):
"""Test parse_int_list with one ints"""
self.assertEquals(parse_int_list("42"),
[42])
def test_empty(self):
"""Test parse_int_list with empty string"""
self.assertEquals(parse_int_list(""),
[])
def test_whitespace(self):
"""Test parse_int_list whitespace variations"""
self.assertEquals(parse_int_list(" 1 2\t 4"),
[1, 2, 4])
self.assertEquals(parse_int_list("1\t2\n4\n"),
[1, 2, 4])
(page 19)
parselist.py
Das zu Testende Modul
def parse_int_list(text):
pass
(page 20)
Testlauf
$ python test_parselist.py
FFFF
======================================================================
FAIL: Test parse_int_list with empty string
----------------------------------------------------------------------
Traceback (most recent call last):
[...]
AssertionError: None != []
======================================================================
FAIL: Test parse_int_list with several ints
[...]
AssertionError: None != [1, 2, 3]
======================================================================
FAIL: Test parse_int_list with one ints
[...]
AssertionError: None != [42]
======================================================================
FAIL: Test parse_int_list with various amounts of whitespace
[...]
AssertionError: None != [1, 2, 4]
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=4)
(page 21)
parselist.py:
Fertige Implementation
def parse_int_list(text):
return [int(num)
for num in text.split()]
(page 22)
Testlauf
$ python test_parselist.py
....
--------------------------------------------
Ran 4 tests in 0.000s
OK
(page 23)
Fehlerbehandlung Testen
def test_non_int(self):
"""Test parse_int_list with non-integers"""
self.assertRaises(ValueError,
parse_int_list, "1.0")
def test_non_number(self):
"""Test parse_int_list with non-numbers"""
self.assertRaises(ValueError,
parse_int_list, "1 abc")
(page 24)
Weitere unittest Features
- weitere Testmethoden
- Auf-/Abbau von zu testenden Datenstrukturen
setUp, tearDown
Erweiterbar durch eigen Klassen
(page 25)
Tests mit doctest
Testet Beispiele in Docstrings
def parse_int_list(text):
"""Parse einen String mit mehreren ganzen Zahlen
Der Eingabestring sollte durch Whitespace
getrennte ganze Zahlen in dezimalschreibweise
enthalten. Der Rückgabewert ist eine Liste mit
entsprechenden Integerobjekten. Beispiel:
>>> from parselist import parse_int_list
>>> parse_int_list('1 2 3')
[1, 2, 3]
"""
return [int(num) for num in text.split()]
(page 26)
Doctest von parselist
Testmodul:
import doctest
import parselist
doctest.testmod(parselist)
Testlauf:
$ python doctest_parselist.py
$
(page 27)
Doctest geschwätzig
$ python doctest_parselist.py -v
Running parselist.__doc__
0 of 0 examples failed in parselist.__doc__
Running parselist.parse_int_list.__doc__
Trying: from parselist import parse_int_list
Expecting: nothing
ok
Trying: parse_int_list('1 2 3')
Expecting: [1, 2, 3]
ok
0 of 2 examples failed in parselist.parse_int_list.__doc__
1 items had no tests:
parselist
1 items passed all tests:
2 tests in parselist.parse_int_list
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
(page 28)
Doctest und Exceptions
def parse_int_list(text):
"""Parse einen String mit mehreren ganzen Zahlen
[...]
Wenn nicht alle Elemente Zahlen sind, gibt es eine
ValueError Exception:
>>> parse_int_list("2 abc")
Traceback (most recent call last):
File "parselist.py", line 15, in parse_int_list
return [int(num) for num in text.split()]
ValueError: invalid literal for int(): abc
"""
return [int(num) for num in text.split()]
(page 29)
Automatische Tests können die Qualität der
Software und der Softwareentwicklung verbessern,
gerade auch in der Freien Software.
Fragen?
(page 30)