Odkryj testowanie oparte na w艂a艣ciwo艣ciach za pomoc膮 biblioteki Hypothesis w Pythonie. Wyjd藕 poza testy oparte na przyk艂adach, aby znale藕膰 przypadki brzegowe i budowa膰 bardziej niezawodne oprogramowanie.
Poza testami jednostkowymi: dog艂臋bne spojrzenie na testowanie oparte na w艂a艣ciwo艣ciach z u偶yciem biblioteki Hypothesis w Pythonie
W 艣wiecie rozwoju oprogramowania testowanie jest podstaw膮 jako艣ci. Od dziesi臋cioleci dominuj膮cym paradygmatem jest testowanie oparte na przyk艂adach. Skrupulatnie tworzymy dane wej艣ciowe, definiujemy oczekiwane dane wyj艣ciowe i piszemy asercje, aby sprawdzi膰, czy nasz kod zachowuje si臋 zgodnie z planem. To podej艣cie, spotykane w frameworkach takich jak unittest
i pytest
, jest pot臋偶ne i niezb臋dne. Ale co, je艣li powiem ci, 偶e istnieje podej艣cie uzupe艂niaj膮ce, kt贸re mo偶e ujawni膰 b艂臋dy, kt贸rych nigdy nawet nie pomy艣la艂e艣 szuka膰?
Witamy w 艣wiecie testowania opartego na w艂a艣ciwo艣ciach, paradygmacie, kt贸ry przesuwa nacisk z testowania konkretnych przyk艂ad贸w na weryfikowanie og贸lnych w艂a艣ciwo艣ci twojego kodu. A w ekosystemie Pythona niekwestionowanym mistrzem tego podej艣cia jest biblioteka o nazwie Hypothesis.
Ten kompleksowy przewodnik poprowadzi ci臋 od ca艂kowicie pocz膮tkuj膮cego do pewnego praktyka testowania opartego na w艂a艣ciwo艣ciach za pomoc膮 Hypothesis. Zbadamy podstawowe koncepcje, zag艂臋bimy si臋 w praktyczne przyk艂ady i nauczymy si臋, jak zintegrowa膰 to pot臋偶ne narz臋dzie z codziennym procesem rozwoju, aby tworzy膰 bardziej solidne, niezawodne i odporne na b艂臋dy oprogramowanie.
Czym jest testowanie oparte na w艂a艣ciwo艣ciach? Zmiana w sposobie my艣lenia
Aby zrozumie膰 Hypothesis, musimy najpierw poj膮膰 fundamentaln膮 ide臋 testowania opartego na w艂a艣ciwo艣ciach. Por贸wnajmy to z tradycyjnym testowaniem opartym na przyk艂adach, kt贸re wszyscy znamy.
Testowanie oparte na przyk艂adach: znana 艣cie偶ka
Wyobra藕 sobie, 偶e napisa艂e艣 niestandardow膮 funkcj臋 sortowania, my_sort()
. Przy testowaniu opartym na przyk艂adach tw贸j proces my艣lowy by艂by nast臋puj膮cy:
- "Przetestujmy to za pomoc膮 prostej, uporz膮dkowanej listy." ->
assert my_sort([1, 2, 3]) == [1, 2, 3]
- "A co z list膮 posortowan膮 w odwrotnej kolejno艣ci?" ->
assert my_sort([3, 2, 1]) == [1, 2, 3]
- "A co z pust膮 list膮?" ->
assert my_sort([]) == []
- "Lista z duplikatami?" ->
assert my_sort([5, 1, 5, 2]) == [1, 2, 5, 5]
- "I lista z liczbami ujemnymi?" ->
assert my_sort([-1, -5, 0]) == [-5, -1, 0]
To jest skuteczne, ale ma fundamentalne ograniczenie: testujesz tylko przypadki, o kt贸rych mo偶esz pomy艣le膰. Twoje testy s膮 tak dobre, jak twoja wyobra藕nia. Mo偶esz pomin膮膰 przypadki brzegowe z udzia艂em bardzo du偶ych liczb, niedok艂adno艣ci zmiennoprzecinkowych, okre艣lonych znak贸w Unicode lub z艂o偶onych kombinacji danych, kt贸re prowadz膮 do nieoczekiwanego zachowania.
Testowanie oparte na w艂a艣ciwo艣ciach: My艣lenie w kategoriach niezmiennik贸w
Testowanie oparte na w艂a艣ciwo艣ciach odwraca sytuacj臋. Zamiast dostarcza膰 konkretne przyk艂ady, definiujesz w艂a艣ciwo艣ci lub niezmienniki swojej funkcji - regu艂y, kt贸re powinny by膰 prawdziwe dla dowolnego prawid艂owego wej艣cia. Dla naszej funkcji my_sort()
te w艂a艣ciwo艣ci mog膮 by膰 nast臋puj膮ce:
- Wyj艣cie jest posortowane: Dla dowolnej listy liczb ka偶dy element na li艣cie wyj艣ciowej jest mniejszy lub r贸wny nast臋pnemu elementowi.
- Wyj艣cie zawiera te same elementy co wej艣cie: Posortowana lista jest tylko permutacj膮 oryginalnej listy; 偶adne elementy nie s膮 dodawane ani tracone.
- Funkcja jest idempotentna: Sortowanie ju偶 posortowanej listy nie powinno jej zmienia膰. Oznacza to, 偶e
my_sort(my_sort(some_list)) == my_sort(some_list)
.
W tym podej艣ciu nie piszesz danych testowych. Piszesz regu艂y. Nast臋pnie pozwalasz frameworkowi, takiemu jak Hypothesis, generowa膰 setki lub tysi膮ce losowych, r贸偶norodnych i cz臋sto podst臋pnych danych wej艣ciowych, aby spr贸bowa膰 udowodni膰, 偶e twoje w艂a艣ciwo艣ci s膮 b艂臋dne. Je艣li znajdzie dane wej艣ciowe, kt贸re 艂ami膮 w艂a艣ciwo艣膰, znalaz艂 b艂膮d.
Wprowadzenie do Hypothesis: Tw贸j automatyczny generator danych testowych
Hypothesis to wiod膮ca biblioteka do testowania opartego na w艂a艣ciwo艣ciach dla Pythona. Pobiera zdefiniowane w艂a艣ciwo艣ci i wykonuje ci臋偶k膮 prac臋 zwi膮zan膮 z generowaniem danych testowych, aby je zakwestionowa膰. To nie tylko generator losowych danych; to inteligentne i pot臋偶ne narz臋dzie zaprojektowane do wydajnego znajdowania b艂臋d贸w.
Kluczowe cechy Hypothesis
- Automatyczne generowanie przypadk贸w testowych: Definiujesz *kszta艂t* potrzebnych danych (np. "lista liczb ca艂kowitych", "ci膮g znak贸w zawieraj膮cy tylko litery", "data i godzina w przysz艂o艣ci"), a Hypothesis generuje szerok膮 gam臋 przyk艂ad贸w zgodnych z tym kszta艂tem.
- Inteligentne zmniejszanie: To jest magiczna funkcja. Kiedy Hypothesis znajdzie przypadek testowy, kt贸ry ko艅czy si臋 niepowodzeniem (np. lista 50 liczb zespolonych, kt贸ra powoduje awari臋 funkcji sortowania), nie tylko zg艂asza t臋 ogromn膮 list臋. Inteligentnie i automatycznie upraszcza dane wej艣ciowe, aby znale藕膰 najmniejszy mo偶liwy przyk艂ad, kt贸ry nadal powoduje awari臋. Zamiast listy 50-elementowej mo偶e zg艂osi膰, 偶e awaria wyst臋puje tylko z
[inf, nan]
. To sprawia, 偶e debugowanie jest niezwykle szybkie i wydajne. - Bezproblemowa integracja: Hypothesis doskonale integruje si臋 z popularnymi frameworkami testowymi, takimi jak
pytest
iunittest
. Mo偶esz dodawa膰 testy oparte na w艂a艣ciwo艣ciach obok istniej膮cych test贸w opartych na przyk艂adach bez zmiany workflow. - Bogata biblioteka strategii: Zawiera ogromn膮 kolekcj臋 wbudowanych "strategii" do generowania wszystkiego, od prostych liczb ca艂kowitych i ci膮g贸w znak贸w po z艂o偶one, zagnie偶d偶one struktury danych, daty i godziny uwzgl臋dniaj膮ce stref臋 czasow膮, a nawet tablice NumPy.
- Testowanie stanowe: W przypadku bardziej z艂o偶onych system贸w Hypothesis mo偶e testowa膰 sekwencje dzia艂a艅, aby znale藕膰 b艂臋dy w przej艣ciach stan贸w, co jest notorycznie trudne w przypadku testowania opartego na przyk艂adach.
Pierwsze kroki: Tw贸j pierwszy test Hypothesis
Zabierzmy si臋 do pracy. Najlepszym sposobem na zrozumienie Hypothesis jest zobaczenie go w akcji.
Instalacja
Najpierw musisz zainstalowa膰 Hypothesis i wybrany program uruchamiaj膮cy testy (u偶yjemy pytest
). To proste jak:
pip install pytest hypothesis
Prosty przyk艂ad: Funkcja warto艣ci bezwzgl臋dnej
Rozwa偶my prost膮 funkcj臋, kt贸ra ma oblicza膰 warto艣膰 bezwzgl臋dn膮 liczby. Nieco b艂臋dna implementacja mo偶e wygl膮da膰 tak:
# w pliku o nazwie `my_math.py` def custom_abs(x): """Niestandardowa implementacja funkcji warto艣ci bezwzgl臋dnej.""" if x < 0: return -x return x
Teraz napiszmy plik testowy, test_my_math.py
. Najpierw tradycyjne podej艣cie pytest
:
# test_my_math.py (oparte na przyk艂adach) def test_abs_positive(): assert custom_abs(5) == 5 def test_abs_negative(): assert custom_abs(-5) == 5 def test_abs_zero(): assert custom_abs(0) == 0
Te testy przechodz膮. Nasza funkcja wygl膮da poprawnie na podstawie tych przyk艂ad贸w. Ale teraz napiszmy test oparty na w艂a艣ciwo艣ciach z Hypothesis. Jaka jest podstawowa w艂a艣ciwo艣膰 funkcji warto艣ci bezwzgl臋dnej? Wynik nigdy nie powinien by膰 ujemny.
# test_my_math.py (oparte na w艂a艣ciwo艣ciach z Hypothesis) from hypothesis import given from hypothesis import strategies as st from my_math import custom_abs @given(st.integers()) def test_abs_property_is_non_negative(x): """W艂a艣ciwo艣膰: warto艣膰 bezwzgl臋dna dowolnej liczby ca艂kowitej jest zawsze >= 0.""" assert custom_abs(x) >= 0
Roz艂贸偶my to na czynniki pierwsze:
from hypothesis import given, strategies as st
: Importujemy niezb臋dne komponenty.given
to dekorator, kt贸ry zamienia zwyk艂膮 funkcj臋 testow膮 w test oparty na w艂a艣ciwo艣ciach.strategies
to modu艂, w kt贸rym znajdujemy nasze generatory danych.@given(st.integers())
: To jest sedno testu. Dekorator@given
m贸wi Hypothesis, aby uruchomi艂 t臋 funkcj臋 testow膮 wiele razy. Dla ka偶dego uruchomienia wygeneruje warto艣膰 za pomoc膮 dostarczonej strategii,st.integers()
i przeka偶e j膮 jako argumentx
do naszej funkcji testowej.assert custom_abs(x) >= 0
: To jest nasza w艂a艣ciwo艣膰. Twierdzimy, 偶e dla jakiejkolwiek liczby ca艂kowitejx
, o kt贸rej Hypothesis marzy, wynik naszej funkcji musi by膰 wi臋kszy lub r贸wny zero.
Kiedy uruchomisz to za pomoc膮 pytest
, prawdopodobnie przejdzie dla wielu warto艣ci. Hypothesis spr贸buje 0, -1, 1, du偶ych liczb dodatnich, du偶ych liczb ujemnych i nie tylko. Nasza prosta funkcja obs艂uguje wszystkie te poprawnie. Teraz spr贸bujmy innej strategii, aby sprawdzi膰, czy mo偶emy znale藕膰 s艂abo艣膰.
# Przetestujmy z liczbami zmiennoprzecinkowymi @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
Je艣li to uruchomisz, Hypothesis szybko znajdzie przypadek niepowodzenia!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
Hypothesis odkry艂, 偶e nasza funkcja, gdy otrzyma float('nan')
(Not a Number), zwraca nan
. Asercja nan >= 0
jest fa艂szywa. W艂a艣nie znale藕li艣my subtelny b艂膮d, o kt贸rym prawdopodobnie nie pomy艣leliby艣my, aby przetestowa膰 go r臋cznie. Mogliby艣my naprawi膰 nasz膮 funkcj臋, aby obs艂u偶y膰 ten przypadek, by膰 mo偶e poprzez wywo艂anie ValueError
lub zwr贸cenie okre艣lonej warto艣ci.
Jeszcze lepiej, co je艣li b艂膮d dotyczy艂by bardzo specyficznej liczby zmiennoprzecinkowej? Reduktor Hypothesis wzi膮艂by du偶膮, z艂o偶on膮 liczb臋 powoduj膮c膮 b艂膮d i zredukowa艂 j膮 do najprostszej mo偶liwej wersji, kt贸ra nadal wyzwala b艂膮d.
Pot臋ga strategii: Tworzenie danych testowych
Strategie s膮 sercem Hypothesis. S膮 to przepisy na generowanie danych. Biblioteka zawiera ogromn膮 gam臋 wbudowanych strategii i mo偶esz je 艂膮czy膰 i dostosowywa膰, aby generowa膰 praktycznie dowoln膮 struktur臋 danych, jak膮 mo偶esz sobie wyobrazi膰.
Typowe wbudowane strategie
- Numeryczne:
st.integers(min_value=0, max_value=1000)
: Generuje liczby ca艂kowite, opcjonalnie w okre艣lonym zakresie.st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False)
: Generuje liczby zmiennoprzecinkowe z precyzyjn膮 kontrol膮 nad warto艣ciami specjalnymi.st.fractions()
,st.decimals()
- Tekst:
st.text(min_size=1, max_size=50)
: Generuje ci膮gi Unicode o okre艣lonej d艂ugo艣ci.st.text(alphabet='abcdef0123456789')
: Generuje ci膮gi znak贸w z okre艣lonego zestawu znak贸w (np. dla kod贸w szesnastkowych).st.characters()
: Generuje pojedyncze znaki.
- Kolekcje:
st.lists(st.integers(), min_size=1)
: Generuje listy, w kt贸rych ka偶dy element jest liczb膮 ca艂kowit膮. Zwr贸膰 uwag臋, jak przekazujemy inn膮 strategi臋 jako argument! Nazywa si臋 to kompozycj膮.st.tuples(st.text(), st.booleans())
: Generuje krotki o sta艂ej strukturze.st.sets(st.integers())
st.dictionaries(keys=st.text(), values=st.integers())
: Generuje s艂owniki z okre艣lonymi typami kluczy i warto艣ci.
- Temporalne:
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. Mo偶na je uwzgl臋dnia膰 w strefie czasowej.
- R贸偶ne:
st.booleans()
: GenerujeTrue
lubFalse
.st.just('constant_value')
: Zawsze generuje t臋 sam膮 pojedyncz膮 warto艣膰. Przydatne do tworzenia z艂o偶onych strategii.st.one_of(st.integers(), st.text())
: Generuje warto艣膰 z jednej z dostarczonych strategii.st.none()
: Generuje tylkoNone
.
艁膮czenie i przekszta艂canie strategii
Prawdziwa moc Hypothesis wynika z jego zdolno艣ci do budowania z艂o偶onych strategii z prostszych.
U偶ywanie .map()
Metoda .map()
pozwala pobra膰 warto艣膰 z jednej strategii i przekszta艂ci膰 j膮 w co艣 innego. Jest to idealne rozwi膮zanie do tworzenia obiekt贸w niestandardowych klas.
# Prosta klasa danych from dataclasses import dataclass @dataclass class User: user_id: int username: str # Strategia generowania obiekt贸w User user_strategy = st.builds( User, user_id=st.integers(min_value=1), username=st.text(min_size=3, alphabet='abcdefghijklmnopqrstuvwxyz') ) @given(user=user_strategy) def test_user_creation(user): assert isinstance(user, User) assert user.user_id > 0 assert user.username.isalpha()
U偶ywanie .filter()
i assume()
Czasami trzeba odrzuci膰 niekt贸re wygenerowane warto艣ci. Na przyk艂ad mo偶esz potrzebowa膰 listy liczb ca艂kowitych, w kt贸rej suma nie wynosi zero. Mo偶esz u偶y膰 .filter()
:
st.lists(st.integers()).filter(lambda x: sum(x) != 0)
Jednak u偶ywanie .filter()
mo偶e by膰 nieefektywne. Je艣li warunek jest cz臋sto fa艂szywy, Hypothesis mo偶e sp臋dzi膰 du偶o czasu na pr贸bach wygenerowania prawid艂owego przyk艂adu. Lepszym podej艣ciem jest cz臋sto u偶ycie assume()
wewn膮trz funkcji testowej:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... logika testu tutaj ...
assume()
m贸wi Hypothesis: "Je艣li ten warunek nie jest spe艂niony, po prostu odrzu膰 ten przyk艂ad i spr贸buj nowego." Jest to bardziej bezpo艣redni i cz臋sto wydajniejszy spos贸b ograniczania danych testowych.
U偶ywanie st.composite()
W przypadku naprawd臋 z艂o偶onego generowania danych, gdzie jedna wygenerowana warto艣膰 zale偶y od innej, st.composite()
jest narz臋dziem, kt贸rego potrzebujesz. Pozwala napisa膰 funkcj臋, kt贸ra przyjmuje specjaln膮 funkcj臋 draw
jako argument, kt贸rej mo偶na u偶y膰 do pobierania warto艣ci z innych strategii krok po kroku.
Klasycznym przyk艂adem jest generowanie listy i prawid艂owego indeksu do tej listy.
@st.composite def list_and_index(draw): # Najpierw narysuj niepust膮 list臋 my_list = draw(st.lists(st.integers(), min_size=1)) # Nast臋pnie narysuj indeks, kt贸ry z pewno艣ci膮 b臋dzie prawid艂owy dla tej listy index = draw(st.integers(min_value=0, max_value=len(my_list) - 1)) return (my_list, index) @given(data=list_and_index()) def test_list_access(data): my_list, index = data # Ten dost臋p jest gwarantowany jako bezpieczny ze wzgl臋du na spos贸b, w jaki zbudowali艣my strategi臋 element = my_list[index] assert element is not None # Prosta asercja
Hypothesis w akcji: Scenariusze z 偶ycia wzi臋te
Zastosujmy te koncepcje do bardziej realistycznych problem贸w, z kt贸rymi programi艣ci borykaj膮 si臋 ka偶dego dnia.
Scenariusz 1: Testowanie funkcji serializacji danych
Wyobra藕 sobie funkcj臋, kt贸ra serializuje profil u偶ytkownika (s艂ownik) do ci膮gu znak贸w bezpiecznego dla adres贸w URL i inn膮, kt贸ra go deserializuje. Kluczow膮 w艂a艣ciwo艣ci膮 jest to, 偶e proces powinien by膰 idealnie odwracalny.
import json import base64 def serialize_profile(data: dict) -> str: """Serializuje s艂ownik do ci膮gu znak贸w base64 bezpiecznego dla adres贸w URL.""" json_string = json.dumps(data) return base64.urlsafe_b64encode(json_string.encode('utf-8')).decode('utf-8') def deserialize_profile(encoded_str: str) -> dict: """Deserializuje ci膮g znak贸w z powrotem do s艂ownika.""" json_string = base64.urlsafe_b64decode(encoded_str.encode('utf-8')).decode('utf-8') return json.loads(json_string) # Teraz test # Potrzebujemy strategii, kt贸ra generuje s艂owniki kompatybilne z JSON json_dictionaries = st.dictionaries( keys=st.text(), values=st.recursive(st.none() | st.booleans() | st.floats(allow_nan=False) | st.text(), lambda children: st.lists(children) | st.dictionaries(st.text(), children), max_leaves=10) ) @given(profile=json_dictionaries) def test_serialization_roundtrip(profile): """W艂a艣ciwo艣膰: Deserializacja zakodowanego profilu powinna zwr贸ci膰 oryginalny profil.""" encoded = serialize_profile(profile) decoded = deserialize_profile(encoded) assert profile == decoded
Ten pojedynczy test zaatakuje nasze funkcje ogromn膮 r贸偶norodno艣ci膮 danych: puste s艂owniki, s艂owniki z zagnie偶d偶onymi listami, s艂owniki ze znakami Unicode, s艂owniki z dziwnymi kluczami i nie tylko. Jest to znacznie dok艂adniejsze ni偶 pisanie kilku r臋cznych przyk艂ad贸w.
Scenariusz 2: Testowanie algorytmu sortowania
Wr贸膰my do naszego przyk艂adu sortowania. Oto jak przetestowa膰 w艂a艣ciwo艣ci, kt贸re zdefiniowali艣my wcze艣niej.
from collections import Counter def my_buggy_sort(numbers): # Wprowad藕my subtelny b艂膮d: pomija duplikaty return sorted(list(set(numbers))) @given(st.lists(st.integers())) def test_sorting_properties(numbers): sorted_list = my_buggy_sort(numbers) # W艂a艣ciwo艣膰 1: Wyj艣cie jest posortowane for i in range(len(sorted_list) - 1): assert sorted_list[i] <= sorted_list[i+1] # W艂a艣ciwo艣膰 2: Elementy s膮 takie same (to znajdzie b艂膮d) assert Counter(numbers) == Counter(sorted_list) # W艂a艣ciwo艣膰 3: Funkcja jest idempotentna assert my_buggy_sort(sorted_list) == sorted_list
Kiedy uruchomisz ten test, Hypothesis szybko znajdzie przypadek niepowodzenia dla W艂a艣ciwo艣ci 2, na przyk艂ad numbers=[0, 0]
. Nasza funkcja zwraca [0]
, a Counter([0, 0])
nie r贸wna si臋 Counter([0])
. Reduktor zapewni, 偶e przyk艂ad niepowodzenia jest tak prosty, jak to mo偶liwe, dzi臋ki czemu przyczyna b艂臋du b臋dzie od razu oczywista.
Scenariusz 3: Testowanie stanowe
W przypadku obiekt贸w ze stanem wewn臋trznym, kt贸ry zmienia si臋 w czasie (takich jak po艂膮czenie z baz膮 danych, koszyk na zakupy lub pami臋膰 podr臋czna), znalezienie b艂臋d贸w mo偶e by膰 niezwykle trudne. Do wyzwolenia b艂臋du mo偶e by膰 wymagana okre艣lona sekwencja operacji. Hypothesis udost臋pnia RuleBasedStateMachine
dok艂adnie do tego celu.
Wyobra藕 sobie proste API dla wbudowanego magazynu klucz-warto艣膰:
class SimpleKeyValueStore: def __init__(self): self._data = {} def set(self, key, value): self._data[key] = value def get(self, key): return self._data.get(key) def delete(self, key): if key in self._data: del self._data[key] def size(self): return len(self._data)
Mo偶emy modelowa膰 jego zachowanie i testowa膰 je za pomoc膮 automatu stan贸w:
from hypothesis.stateful import RuleBasedStateMachine, rule, Bundle class KeyValueStoreMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.model = {} self.sut = SimpleKeyValueStore() # Bundle() s艂u偶y do przekazywania danych mi臋dzy regu艂ami keys = Bundle('keys') @rule(target=keys, key=st.text(), value=st.integers()) def set_key(self, key, value): self.model[key] = value self.sut.set(key, value) return key @rule(key=keys) def delete_key(self, key): del self.model[key] self.sut.delete(key) @rule(key=st.text()) def get_key(self, key): model_val = self.model.get(key) sut_val = self.sut.get(key) assert model_val == sut_val @rule() def check_size(self): assert len(self.model) == self.sut.size() # Aby uruchomi膰 test, po prostu dziedzicz po maszynie i unittest.TestCase # W pytest mo偶esz po prostu przypisa膰 test do klasy maszyny TestKeyValueStore = KeyValueStoreMachine.TestCase
Hypothesis wykona teraz losowe sekwencje operacji set_key
, delete_key
, get_key
i check_size
, nieustannie pr贸buj膮c znale藕膰 sekwencj臋, kt贸ra spowoduje niepowodzenie jednej z asercji. Sprawdzi, czy pobieranie usuni臋tego klucza zachowuje si臋 poprawnie, czy rozmiar jest sp贸jny po wielu ustawieniach i usuni臋ciach oraz wiele innych scenariuszy, o kt贸rych mo偶esz nie pomy艣le膰, aby przetestowa膰 je r臋cznie.
Najlepsze praktyki i zaawansowane wskaz贸wki
- Baza danych przyk艂ad贸w: Hypothesis jest sprytny. Kiedy znajdzie b艂膮d, zapisuje przyk艂ad niepowodzenia w lokalnym katalogu (
.hypothesis/
). Nast臋pnym razem, gdy uruchomisz testy, najpierw odtworzy ten przyk艂ad niepowodzenia, daj膮c ci natychmiastow膮 informacj臋, 偶e b艂膮d nadal wyst臋puje. Po naprawieniu przyk艂ad nie jest ju偶 odtwarzany. - Kontrolowanie wykonywania test贸w za pomoc膮
@settings
: Mo偶esz kontrolowa膰 wiele aspekt贸w uruchomienia testu za pomoc膮 dekoratora@settings
. Mo偶esz zwi臋kszy膰 liczb臋 przyk艂ad贸w, ustawi膰 termin, jak d艂ugo mo偶e dzia艂a膰 pojedynczy przyk艂ad (aby wy艂apa膰 niesko艅czone p臋tle) i wy艂膮czy膰 niekt贸re kontrole stanu.@settings(max_examples=500, deadline=1000) # Uruchom 500 przyk艂ad贸w, 1-sekundowy termin @given(...) ...
- Odtwarzanie b艂臋d贸w: Ka偶de uruchomienie Hypothesis wypisuje warto艣膰 pocz膮tkow膮 (np.
@reproduce_failure('version', 'seed')
). Je艣li serwer CI znajdzie b艂膮d, kt贸rego nie mo偶esz odtworzy膰 lokalnie, mo偶esz u偶y膰 tego dekoratora z dostarczon膮 warto艣ci膮 pocz膮tkow膮, aby wymusi膰 na Hypothesis uruchomienie dok艂adnie tej samej sekwencji przyk艂ad贸w. - Integracja z CI/CD: Hypothesis idealnie pasuje do dowolnego potoku ci膮g艂ej integracji. Jego zdolno艣膰 do znajdowania niejasnych b艂臋d贸w, zanim dotr膮 one do produkcji, czyni go nieocenion膮 siatk膮 bezpiecze艅stwa.
Zmiana nastawienia: My艣lenie w kategoriach w艂a艣ciwo艣ci
Wdra偶anie Hypothesis to co艣 wi臋cej ni偶 tylko nauka nowej biblioteki; chodzi o przyj臋cie nowego sposobu my艣lenia o poprawno艣ci kodu. Zamiast pyta膰: "Jakie dane wej艣ciowe powinienem przetestowa膰?", zaczynasz pyta膰: "Jakie s膮 uniwersalne prawdy o tym kodzie?"Oto kilka pyta艅, kt贸re pomog膮 ci w identyfikacji w艂a艣ciwo艣ci:
- Czy istnieje operacja odwrotna? (np. serializacja/deserializacja, szyfrowanie/deszyfrowanie, kompresja/dekompresja). W艂a艣ciwo艣ci膮 jest to, 偶e wykonanie operacji i jej odwrotno艣ci powinno da膰 oryginalne dane wej艣ciowe.
- Czy operacja jest idempotentna? (np.
abs(abs(x)) == abs(x)
). Zastosowanie funkcji wi臋cej ni偶 raz powinno da膰 taki sam wynik, jak zastosowanie jej raz. - Czy istnieje inny, prostszy spos贸b obliczenia tego samego wyniku? Mo偶esz sprawdzi膰, czy twoja z艂o偶ona, zoptymalizowana funkcja daje takie samo wyj艣cie, jak prosta, oczywista wersja (np. testowanie twojego fantazyjnego sortowania wzgl臋dem wbudowanego w Pythona
sorted()
). - Co powinno by膰 zawsze prawd膮 o wyj艣ciu? (np. wyj艣cie funkcji
find_prime_factors
powinno zawiera膰 tylko liczby pierwsze, a ich iloczyn powinien by膰 r贸wny wej艣ciu). - Jak zmienia si臋 stan? (W przypadku testowania stanowego) Jakie niezmienniki musz膮 by膰 utrzymywane po ka偶dej prawid艂owej operacji? (np. liczba pozycji w koszyku na zakupy nigdy nie mo偶e by膰 ujemna).
Wniosek: Nowy poziom pewno艣ci
Testowanie oparte na w艂a艣ciwo艣ciach z Hypothesis nie zast臋puje testowania opartego na przyk艂adach. Nadal potrzebujesz konkretnych, r臋cznie pisanych test贸w dla krytycznej logiki biznesowej i dobrze zrozumia艂ych wymaga艅 (np. "U偶ytkownik z kraju X musi widzie膰 cen臋 Y").
To, co zapewnia Hypothesis, to pot臋偶ny, zautomatyzowany spos贸b eksploracji zachowania twojego kodu i ochrony przed nieprzewidzianymi przypadkami brzegowymi. Dzia艂a jako niestrudzony partner, generuj膮c tysi膮ce test贸w, kt贸re s膮 bardziej r贸偶norodne i podst臋pne ni偶 jakikolwiek cz艂owiek m贸g艂by realistycznie napisa膰. Definiuj膮c fundamentalne w艂a艣ciwo艣ci twojego kodu, tworzysz solidn膮 specyfikacj臋, z kt贸r膮 Hypothesis mo偶e testowa膰, daj膮c ci nowy poziom pewno艣ci w twoim oprogramowaniu.
Nast臋pnym razem, gdy napiszesz funkcj臋, po艣wi臋膰 chwil臋, aby pomy艣le膰 poza przyk艂adami. Zapytaj siebie: "Jakie s膮 zasady? Co musi by膰 zawsze prawd膮?" Nast臋pnie pozw贸l Hypothesis wykona膰 ci臋偶k膮 prac臋 zwi膮zan膮 z pr贸b膮 ich z艂amania. B臋dziesz zaskoczony tym, co znajdzie, a tw贸j kod b臋dzie lepszy dzi臋ki temu.