(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


Zielgruppe

(page 2)


Inhalt



(page 3)


Software Tests

aber:

Abhilfe: Tests automatisieren


(page 4)


Automatische Tests


(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



(page 7)


Entwicklungsprozess


Grundregel 2:

Zu jedem neuen Feature gehören neue Tests



(page 8)


Entwicklungsprozess


Grundregel 3:

Zu jedem neuen Bugfix gehört ein Test


(page 9)


Design


Tests

Getesteter Code

(page 10)


Klassifikation von Tests


Nach Granularität

Nach Kopplungsgrad

(page 11)


Einführung einer Testsuite


Einfach bei neuen Projekten


(page 12)


Einführung einer Testsuite


Schwieriger bei bestehenden Projekten



(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


(page 14)


Schwer Testbare Komponenten



(page 15)


Automatische Tests mit Python


Zwei Module in der Standardbibliothek


(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




(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)