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.