Hypothesis: Property -basiertes Testen¶
In diesem Notebook verwenden wir Property -basierte Tests, um Probleme in unserem Code zu finden. Hypothesis ist eine Bibliothek, die Haskells Quickcheck ähnelt. Später lernen wir sie zusammen mit anderen Testbibliotheken noch genauer kennen: Hypothesis. Hypothesis kann auch Mock-Objekte und Tests für Numpy-Datentypen bereitstellen.
1. Importe¶
[1]:
import re
from hypothesis import assume, given
from hypothesis.strategies import emails, integers, tuples
2. Bereich finden¶
[2]:
def calculate_range(tuple_obj):
return max(tuple_obj) - min(tuple_obj)
3. Test mit strategies
und given
¶
Mit hypothesis.strategies könnt ihr unterschiedliche Testdaten erstellen. Hierfür beitet Hypothesis Strategien für die meisten Typen und Argumente schränken die Möglichkeiten ein um sie euren Erfordernissen anzupassen. Im Beispiel unten verwenden wir die integers-Strategie, die mit dem
Python-Decorator @given
auf die Funktion angewendet wird. Genauer nimmt er unsere Testfunktion und wandelt sie in eine parametrisierte um sie über weite Bereiche passender Daten auszuführen:
[3]:
@given(tuples(integers(), integers(), integers()))
def test_calculate_range(tup):
result = calculate_range(tup)
assert isinstance(result, int)
assert result > 0
[4]:
test_calculate_range()
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[4], line 1
----> 1 test_calculate_range()
Cell In[3], line 2, in test_calculate_range()
1 @given(tuples(integers(), integers(), integers()))
----> 2 def test_calculate_range(tup):
3 result = calculate_range(tup)
4 assert isinstance(result, int)
[... skipping hidden 1 frame]
Cell In[3], line 5, in test_calculate_range(tup)
3 result = calculate_range(tup)
4 assert isinstance(result, int)
----> 5 assert result > 0
AssertionError:
Falsifying example: test_calculate_range(
tup=(0, 0, 0),
)
Nun korrigieren wir den Test mit >=
und überprüfen ihn erneut:
[5]:
@given(tuples(integers(), integers()))
def test_calculate_range(tup):
result = calculate_range(tup)
assert isinstance(result, int)
assert result >= 0
[6]:
test_calculate_range()
3. Gegen Reguläre Ausdrücke prüfen¶
Mit regulären Ausrücken (engl.: regular expressions) lassen sich Zeichenketten auf bestimmte syntaktische Regeln überprüfen. In Python könnt ihr zum Überprüfen regulärer Ausdrücke re.match verwenden.
Hinweis:
Auf der Website regex101 könnt ihr zunächst eure regulären Ausdrücke ausprobieren.
Als Beispiel versuchen wir, aus E-Mail-Adressen username
und die domain
zu ermitteln:
[7]:
def parse_email(email):
result = re.match("(?P<username>\w+).(?P<domain>[\w\.]+)", email).groups()
return result
Nun schreiben wir einen Test test_parse_email
zum Überprüfen unserer Methode. Als Eingabewerte verwenden wir die emails-Strategie von Hypothesis. Als result
erwarten wir z.B.:
('0', 'A.com')
('F', 'j.EeHNqsx')
…
Im Test nehmen wir einerseits an, dass immer zwei Einträge zurückgegeben werden und im zweiten Eintrag ein Punkt (.
) vorkommt.
[8]:
@given(emails())
def test_parse_email(email):
result = parse_email(email)
# print(result)
assert len(result) == 2
assert '.' in result[1]
[9]:
test_parse_email()
---------------------------------------------------------------------------
ExceptionGroup Traceback (most recent call last)
Cell In[9], line 1
----> 1 test_parse_email()
Cell In[8], line 2, in test_parse_email()
1 @given(emails())
----> 2 def test_parse_email(email):
3 result = parse_email(email)
4 # print(result)
File ~/.local/share/virtualenvs/python-311-6zxVKbDJ/lib/python3.11/site-packages/hypothesis/core.py:1374, in given.<locals>.run_test_as_given.<locals>.wrapped_test(*arguments, **kwargs)
1361 # The dance here is to avoid showing users long tracebacks
1362 # full of Hypothesis internals they don't care about.
1363 # We have to do this inline, to avoid adding another
(...)
1367 # which will actually appear in tracebacks is as clear as
1368 # possible - "raise the_error_hypothesis_found".
1369 the_error_hypothesis_found = e.with_traceback(
1370 None
1371 if isinstance(e, BaseExceptionGroup)
1372 else get_trimmed_traceback()
1373 )
-> 1374 raise the_error_hypothesis_found
1376 if not (ran_explicit_examples or state.ever_executed):
1377 raise SKIP_BECAUSE_NO_EXAMPLES
ExceptionGroup: Hypothesis found 2 distinct failures. (2 sub-exceptions)
Mit Hypothesis wurden zwei Beispiele gefunden, die deutlich machen, dass unser regulärer Ausdruck in der parse_email
-Methode noch nicht hinreichend ist: 0/0@A.ac
und /@A.ac
. Nachdem wir unseren regulären Ausdruck entsprechend angepasst haben, können wir den Test erneut aufrufen:
[10]:
def parse_email(email):
result = re.match(
"(?P<username>[\.\w\-\!~#$%&\|{}\+\/\^\`\=\*']+).(?P<domain>[\w\.\-]+)",
email,
).groups()
return result
[11]:
test_parse_email()