test tdd property‐based testing - ghdrako/doc_snipets GitHub Wiki
tags:
Test tdd property‐based testing
| Język | Framework / narzędzie | Uwagi |
|---|---|---|
| Python | Hypothesis | ⭐ bardzo popularny, świetny shrinking |
| Java | jqwik | Integruje się z JUnit 5 |
| Java | QuickTheories | Generatory danych, property testing |
| JavaScript/TypeScript | fast-check | Bardzo popularny |
| Haskell | QuickCheck | Klasyka property-based testing |
| Scala | ScalaCheck | Bardzo dojrzały |
| Rust | proptest | Popularny w Rust |
| Rust | quickcheck | Port koncepcji QuickCheck |
| Go | rapid | Property-based testing dla Go |
| .NET / C# | FsCheck | Mocny framework PBT |
agent może nie tylko generować test cases, ale też proponować same properties, które potem framework automatycznie sprawdza.
Kasyczne testy
assert sortuj([3, 1, 2]) == [1, 2, 3]
assert sortuj([]) == []
assert sortuj([5]) == [5]
Zamiast tego definiujesz zasadę:
wynik sortowania zawsze powinien być uporządkowany i zawierać dokładnie te same elementy co wejście.
Framework generuje setki/tysiące różnych przypadków:
[3,1,2]
[]
[999,-1,5,5,2]
[...]
losowe 100 elementów
losowe 1000 elementów
...
i dla każdego sprawdza:
sortuj(x) jest uporządkowane
AND
sortuj(x) zawiera te same elementy co x
Dla funkcji:
encode(x) → zakodowane dane decode(x) → dane oryginalne
możesz zdefiniować property:
decode(encode(x)) == x
I teraz framework generuje:
x = ""
x = "abc"
x = "ąęłó"
x = "123456789"
x = losowy string
x = bardzo długi string
x = znaki specjalne
...
i sprawdza:
decode(encode(x)) == x
W kontekście baz danych
Można to zastosować np. do sprawdzania, czy dwie wersje zapytania są równoważne.
Masz:
-- wersja A
WHERE status IN ('A', 'V')
i:
-- wersja B
WHERE status = ANY(ARRAY['A','V'])
Property mogłoby być:
dla dowolnego zestawu danych wynik A = wynik B.
Następnie generujesz różne dane:
A
V
X
NULL
''
...
i porównujesz wyniki obu zapytań. To właśnie jest myślenie property-based: nie pytamy „czy działa dla tych 5 przykładów?”, tylko „jaka zasada musi być prawdziwa dla każdego poprawnego wejścia?”.
Przykład — Python + Hypothesis
Załóżmy funkcję:
def sort_and_unique(values):
return sorted(set(values))
Zamiast pisać:
assert sort_and_unique([3, 1, 2]) == [1, 2, 3]
assert sort_and_unique([5, 5, 2]) == [2, 5]
assert sort_and_unique([]) == []
możemy powiedzieć:
dla dowolnej listy liczb wynik ma być posortowany i nie może zawierać duplikatów.
from hypothesis import given
from hypothesis.strategies import lists, integers
@given(lists(integers()))
def test_result_is_sorted_and_unique(values):
result = sort_and_unique(values)
assert result == sorted(result)
assert len(result) == len(set(result))
Hypothesis sam wygeneruje np.:
[]
[1]
[5, 5, 2]
[-100, 0, 100]
[3, 3, 3, 1, 2, 1]
...
i będzie próbował znaleźć przypadek łamiący property.
Najciekawsza rzecz: shrinking
To jest jedna z największych zalet PBT.
Jeżeli framework znajdzie błąd dla:
[918273, -123, 5, 888, 5, 17, ...]
to nie kończy na pokazaniu ogromnego przypadku. Próbuje go zminimalizować do czegoś w rodzaju:
[1, 1]
czyli do najprostszego przypadku, który nadal powoduje błąd.