Avastage omaduspõhine testimine Pythoni Hypothesis teegiga. Liikuge näitepõhistest testidest edasi, et leida äärmusjuhtumeid ja ehitada vastupidavamat ja usaldusväärsemat tarkvara.
Üksiktestidest edasi: Süvitsiminek omaduspõhisesse testimisse Pythoni Hypothesis teegiga
Tarkvaraarenduse maailmas on testimine kvaliteedi nurgakivi. Aastakümneid on domineerivaks paradigmaks olnud näitepõhine testimine. Me loome hoolikalt sisendeid, määratleme oodatavad väljundid ja kirjutame väiteid, et kontrollida, kas meie kood käitub plaanipäraselt. See lähenemine, mida leidub raamistikes nagu unittest
ja pytest
, on võimas ja hädavajalik. Aga mis siis, kui ma ütleksin teile, et on olemas täiendav lähenemine, mis suudab paljastada vigu, mille otsimisele te poleks isegi mõelnud?
Tere tulemast omaduspõhise testimise maailma – paradigma, mis nihutab fookuse konkreetsete näidete testimiselt koodi üldiste omaduste kontrollimisele. Ja Pythoni ökosüsteemis on selle lähenemise vaieldamatu meister teek nimega Hypothesis.
See põhjalik juhend viib teid täielikust algajast enesekindla omaduspõhise testimise praktikuni Hypothesise abil. Uurime põhimõisteid, süveneme praktilistesse näidetesse ja õpime, kuidas integreerida see võimas tööriist oma igapäevasesse arendustöövoogu, et ehitada vastupidavamat, usaldusväärsemat ja vigadekindlamat tarkvara.
Mis on omaduspõhine testimine? Mõtteviisi muutus
Et mõista Hypothesist, peame kõigepealt aru saama omaduspõhise testimise põhiideest. Võrdleme seda traditsioonilise näitepõhise testimisega, mida me kõik tunneme.
Näitepõhine testimine: tuttav rada
Kujutage ette, et olete kirjutanud kohandatud sorteerimisfunktsiooni, my_sort()
. Näitepõhise testimise puhul oleks teie mõttekäik järgmine:
- "Testime seda lihtsa, järjestatud nimekirjaga." ->
assert my_sort([1, 2, 3]) == [1, 2, 3]
- "Aga vastupidises järjekorras nimekirjaga?" ->
assert my_sort([3, 2, 1]) == [1, 2, 3]
- "Kuidas on lood tĂĽhja nimekirjaga?" ->
assert my_sort([]) == []
- "Duplikaatidega nimekiri?" ->
assert my_sort([5, 1, 5, 2]) == [1, 2, 5, 5]
- "Ja nimekiri negatiivsete arvudega?" ->
assert my_sort([-1, -5, 0]) == [-5, -1, 0]
See on tõhus, kuid sellel on fundamentaalne piirang: te testite ainult neid juhtumeid, mille peale te tulete. Teie testid on ainult nii head, kui on teie kujutlusvõime. Te võite mööda vaadata äärmusjuhtumitest, mis hõlmavad väga suuri arve, ujukomaarvude ebatäpsusi, spetsiifilisi unicode'i märke või andmete keerulisi kombinatsioone, mis viivad ootamatu käitumiseni.
Omaduspõhine testimine: mõtlemine invariantides
Omaduspõhine testimine pöörab stsenaariumi pea peale. Spetsiifiliste näidete pakkumise asemel määratlete oma funktsiooni omadused, ehk invariandid – reeglid, mis peaksid kehtima iga kehtiva sisendi puhul. Meie my_sort()
funktsiooni jaoks võivad need omadused olla:
- Väljund on sorteeritud: Iga arvnimekirja puhul on iga element väljundnimekirjas väiksem või võrdne sellele järgnevaga.
- Väljund sisaldab samu elemente kui sisend: Sorteeritud nimekiri on lihtsalt algse nimekirja permutatsioon; elemente ei lisata ega kaotata.
- Funktsioon on idempotentne: Juba sorteeritud nimekirja sorteerimine ei tohiks seda muuta. See tähendab,
my_sort(my_sort(some_list)) == my_sort(some_list)
.
Selle lähenemisviisiga ei kirjuta te testiandmeid. Te kirjutate reegleid. Seejärel lasete raamistikul, nagu Hypothesis, genereerida sadu või tuhandeid juhuslikke, mitmekesiseid ja sageli salakavalaid sisendeid, et püüda teie omadusi ümber lükata. Kui see leiab sisendi, mis rikub omadust, on see leidnud vea.
Tutvustame Hypothesist: Teie automatiseeritud testiandmete generaator
Hypothesis on Pythoni peamine omaduspõhise testimise teek. See võtab teie määratletud omadused ja teeb ära raske töö, genereerides testiandmeid nende proovilepanekuks. See ei ole lihtsalt juhuslike andmete generaator; see on intelligentne ja võimas tööriist, mis on loodud vigade tõhusaks leidmiseks.
Hypothesise peamised omadused
- Automaatne testjuhtumite genereerimine: Teie määratlete vajalike andmete *kuju* (nt "täisarvude nimekiri," "string, mis sisaldab ainult tähti," "tulevikus olev kuupäev ja kellaaeg"), ja Hypothesis genereerib laia valiku sellele kujule vastavaid näiteid.
- Intelligentne lihtsustamine: See on maagiline funktsioon. Kui Hypothesis leiab nurjunud testjuhtumi (nt 50 keerulise arvu nimekiri, mis põhjustab teie sorteerimisfunktsiooni kokkujooksmise), ei teata see lihtsalt sellest massiivsest nimekirjast. See lihtsustab arukalt ja automaatselt sisendit, et leida väikseim võimalik näide, mis vea endiselt põhjustab. 50-elemendilise nimekirja asemel võib see teatada, et viga ilmneb näiteks ainult
[inf, nan]
-iga. See muudab silumise uskumatult kiireks ja tõhusaks. - Sujuv integratsioon: Hypothesis integreerub suurepäraselt populaarsete testimisraamistikega nagu
pytest
jaunittest
. Saate lisada omaduspõhiseid teste oma olemasolevate näitepõhiste testide kõrvale oma töövoogu muutmata. - Rikkalik strateegiate teek: Sellega kaasneb suur hulk sisseehitatud "strateegiaid" kõige genereerimiseks alates lihtsatest täisarvudest ja stringidest kuni keerukate, pesastatud andmestruktuuride, ajavöönditeadlike kuupäevade ja kellaaegade ning isegi NumPy massiivideni.
- Olekupõhine testimine: Keerukamate süsteemide jaoks saab Hypothesis testida tegevuste jadasid, et leida vigu olekute üleminekutes – midagi, mis on näitepõhise testimisega kurikuulsalt raske.
Alustamine: Teie esimene Hypothesis test
Hakkame pihta. Parim viis Hypothesise mõistmiseks on näha seda tegevuses.
Paigaldamine
Kõigepealt peate paigaldama Hypothesise ja oma valitud testikäivitaja (me kasutame pytest
-i). See on niisama lihtne kui:
pip install pytest hypothesis
Lihtne näide: absoluutväärtuse funktsioon
Vaatleme lihtsat funktsiooni, mis peaks arvutama arvu absoluutväärtuse. Veidi vigane implementatsioon võib välja näha selline:
# failis nimega `my_math.py` def custom_abs(x): """Absoluutväärtuse funktsiooni kohandatud implementatsioon.""" if x < 0: return -x return x
NĂĽĂĽd kirjutame testifaili, test_my_math.py
. Kõigepealt traditsiooniline pytest
-i lähenemine:
# test_my_math.py (Näitepõhine) 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
Need testid läbivad. Meie funktsioon tundub nende näidete põhjal õige. Aga nüüd kirjutame omaduspõhise testi Hypothesise abil. Mis on absoluutväärtuse funktsiooni põhiomadus? Tulemus ei tohi kunagi olla negatiivne.
# test_my_math.py (Omaduspõhine Hypothesise abil) 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): """Omadus: Iga täisarvu absoluutväärtus on alati >= 0.""" assert custom_abs(x) >= 0
Vaatame seda lähemalt:
from hypothesis import given, strategies as st
: Impordime vajalikud komponendid.given
on dekoraator, mis muudab tavalise testifunktsiooni omaduspõhiseks testiks.strategies
on moodul, kust leiame oma andmegeneraatorid.@given(st.integers())
: See on testi tuum.@given
dekoraator käsib Hypothesisel seda testifunktsiooni mitu korda käivitada. Iga käivitamise jaoks genereerib see väärtuse, kasutades pakutud strateegiat,st.integers()
, ja edastab selle argumendinax
meie testifunktsioonile.assert custom_abs(x) >= 0
: See on meie omadus. Me väidame, et olenemata sellest, millise täisarvux
Hypothesis välja mõtleb, peab meie funktsiooni tulemus olema suurem või võrdne nulliga.
Kui käivitate selle pytest
-iga, läbib see tõenäoliselt paljude väärtuste puhul. Hypothesis proovib 0, -1, 1, suuri positiivseid arve, suuri negatiivseid arve ja muud. Meie lihtne funktsioon saab nendega kõigiga korrektselt hakkama. Nüüd proovime teist strateegiat, et näha, kas suudame leida nõrkuse.
# Testime ujukomaarvudega @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
Kui te selle käivitate, leiab Hypothesis kiiresti nurjunud juhtumi!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
Hypothesis avastas, et meie funktsioon, saades sisendiks float('nan')
(Not a Number), tagastab nan
. Väide nan >= 0
on väär. Oleme just leidnud peene vea, mida me tõenäoliselt poleks käsitsi testimiseks välja mõelnud. Saaksime oma funktsiooni parandada, et see juhtumit käsitleda, näiteks tõstes ValueError
erindi või tagastades konkreetse väärtuse.
Veelgi parem, mis siis, kui viga oleks olnud väga spetsiifilise ujukomaarvuga? Hypothesise lihtsustaja oleks võtnud suure, keerulise nurjunud arvu ja vähendanud selle lihtsaimaks võimalikuks versiooniks, mis vea endiselt esile kutsub.
Strateegiate jõud: Teie testiandmete loomine
Strateegiad on Hypothesise sĂĽda. Need on retseptid andmete genereerimiseks. Teek sisaldab laia valikut sisseehitatud strateegiaid ning te saate neid kombineerida ja kohandada, et genereerida praktiliselt iga andmestruktuur, mida suudate ette kujutada.
Levinud sisseehitatud strateegiad
- Numbrilised:
st.integers(min_value=0, max_value=1000)
: Genereerib täisarve, valikuliselt kindlas vahemikus.st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False)
: Genereerib ujukomaarve, peenhäälestatud kontrolliga eriväärtuste üle.st.fractions()
,st.decimals()
- Tekst:
st.text(min_size=1, max_size=50)
: Genereerib unicode'i stringe teatud pikkusega.st.text(alphabet='abcdef0123456789')
: Genereerib stringe kindlast märgistikust (nt hex-koodide jaoks).st.characters()
: Genereerib üksikuid märke.
- Kogumid:
st.lists(st.integers(), min_size=1)
: Genereerib nimekirju, kus iga element on täisarv. Pange tähele, kuidas me edastame argumendina teise strateegia! Seda nimetatakse kompositsiooniks.st.tuples(st.text(), st.booleans())
: Genereerib fikseeritud struktuuriga ennikuid.st.sets(st.integers())
st.dictionaries(keys=st.text(), values=st.integers())
: Genereerib sõnastikke määratud võtme- ja väärtusetüüpidega.
- Ajalised:
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. Neid saab muuta ajavöönditeadlikeks.
- Mitmesugust:
st.booleans()
: GenereeribTrue
võiFalse
.st.just('constant_value')
: Genereerib alati sama üksiku väärtuse. Kasulik keerukate strateegiate koostamisel.st.one_of(st.integers(), st.text())
: Genereerib väärtuse ühest pakutud strateegiast.st.none()
: Genereerib ainultNone
.
Strateegiate kombineerimine ja teisendamine
Hypothesise tõeline jõud tuleneb selle võimest ehitada lihtsamatest strateegiatest keerukamaid.
.map()
kasutamine
Meetod .map()
võimaldab teil võtta väärtuse ühest strateegiast ja teisendada see millekski muuks. See on ideaalne teie kohandatud klasside objektide loomiseks.
# Lihtne andmeklass from dataclasses import dataclass @dataclass class User: user_id: int username: str # Strateegia User objektide genereerimiseks 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()
.filter()
ja assume()
kasutamine
Mõnikord peate teatud genereeritud väärtused tagasi lükkama. Näiteks võib teil vaja minna täisarvude nimekirja, mille summa ei ole null. Võiksite kasutada .filter()
:
st.lists(st.integers()).filter(lambda x: sum(x) != 0)
Kuid .filter()
kasutamine võib olla ebaefektiivne. Kui tingimus on sageli väär, võib Hypothesis kulutada palju aega kehtiva näite genereerimisele. Parem lähenemine on sageli kasutada assume()
oma testifunktsiooni sees:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... teie testiloogika siin ...
assume()
ütleb Hypothesisele: "Kui see tingimus ei ole täidetud, siis lihtsalt hülga see näide ja proovi uut." See on otsesem ja sageli tulemuslikum viis oma testiandmete piiramiseks.
st.composite()
kasutamine
Tõeliselt keeruka andmete genereerimiseks, kus üks genereeritud väärtus sõltub teisest, on st.composite()
tööriist, mida vajate. See võimaldab teil kirjutada funktsiooni, mis võtab argumendiks spetsiaalse draw
funktsiooni, mida saate kasutada väärtuste tõmbamiseks teistest strateegiatest samm-sammult.
Klassikaline näide on nimekirja ja sellesse nimekirja sobiva kehtiva indeksi genereerimine.
@st.composite def list_and_index(draw): # Esmalt, tõmba mittetühi nimekiri my_list = draw(st.lists(st.integers(), min_size=1)) # Seejärel, tõmba indeks, mis on garanteeritult selle nimekirja jaoks kehtiv 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 # See pöördumine on garanteeritult ohutu tänu sellele, kuidas me strateegia ehitasime element = my_list[index] assert element is not None # Lihtne väide
Hypothesis tegevuses: reaalse maailma stsenaariumid
Rakendame neid kontseptsioone realistlikumatele probleemidele, millega tarkvaraarendajad iga päev silmitsi seisavad.
Stsenaarium 1: Andmete serialiseerimisfunktsiooni testimine
Kujutage ette funktsiooni, mis serialiseerib kasutajaprofiili (sõnastiku) URL-ohutuks stringiks ja teist, mis selle deserialiseerib. Oluline omadus on, et protsess peaks olema täielikult pööratav.
import json import base64 def serialize_profile(data: dict) -> str: """Serialiseerib sõnastiku URL-ohutuks base64 stringiks.""" json_string = json.dumps(data) return base64.urlsafe_b64encode(json_string.encode('utf-8')).decode('utf-8') def deserialize_profile(encoded_str: str) -> dict: """Deserialiseerib stringi tagasi sõnastikuks.""" json_string = base64.urlsafe_b64decode(encoded_str.encode('utf-8')).decode('utf-8') return json.loads(json_string) # Nüüd testi juurde # Vajame strateegiat, mis genereerib JSON-iga ühilduvaid sõnastikke 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): """Omadus: Kodeeritud profiili deserialiseerimine peaks tagastama algse profiili.""" encoded = serialize_profile(profile) decoded = deserialize_profile(encoded) assert profile == decoded
See üksainus test pommitab meie funktsioone tohutult mitmekesiste andmetega: tühjad sõnastikud, pesastatud nimekirjadega sõnastikud, unicode'i märkidega sõnastikud, kummaliste võtmetega sõnastikud ja palju muud. See on palju põhjalikum kui mõne käsitsi näite kirjutamine.
Stsenaarium 2: Sorteerimisalgoritmi testimine
Tuleme tagasi meie sorteerimisnäite juurde. Siin on, kuidas testiksite omadusi, mida me varem määratlesime.
from collections import Counter def my_buggy_sort(numbers): # Lisame peene vea: see eemaldab duplikaadid return sorted(list(set(numbers))) @given(st.lists(st.integers())) def test_sorting_properties(numbers): sorted_list = my_buggy_sort(numbers) # Omadus 1: Väljund on sorteeritud for i in range(len(sorted_list) - 1): assert sorted_list[i] <= sorted_list[i+1] # Omadus 2: Elemendid on samad (see leiab vea) assert Counter(numbers) == Counter(sorted_list) # Omadus 3: Funktsioon on idempotentne assert my_buggy_sort(sorted_list) == sorted_list
Selle testi käivitamisel leiab Hypothesis kiiresti omaduse 2 jaoks nurjunud näite, näiteks numbers=[0, 0]
. Meie funktsioon tagastab [0]
ja Counter([0, 0])
ei võrdu Counter([0])
. Lihtsustaja tagab, et nurjunud näide on võimalikult lihtne, muutes vea põhjuse koheselt ilmseks.
Stsenaarium 3: Olekupõhine testimine
Objektide puhul, millel on sisemine olek, mis aja jooksul muutub (nagu andmebaasiühendus, ostukorv või vahemälu), võib vigade leidmine olla uskumatult raske. Vea esilekutsumiseks võib olla vajalik teatud operatsioonide jada. Hypothesis pakub just selleks otstarbeks `RuleBasedStateMachine`.
Kujutage ette lihtsat API-d mälusisesele võtme-väärtuse hoidlale:
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)
Saame selle käitumist modelleerida ja seda olekumasinaga testida:
from hypothesis.stateful import RuleBasedStateMachine, rule, Bundle class KeyValueStoreMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.model = {} self.sut = SimpleKeyValueStore() # Bundle()-it kasutatakse andmete edastamiseks reeglite vahel 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() # Testi käivitamiseks tuleb lihtsalt masinast ja unittest.TestCase-ist alamklass luua # Pytestis saate testi lihtsalt masinaklassile määrata TestKeyValueStore = KeyValueStoreMachine.TestCase
Hypothesis hakkab nüüd täitma juhuslikke set_key
, delete_key
, get_key
ja check_size
operatsioonide jadasid, püüdes lakkamatult leida jada, mis põhjustab ühe väite nurjumise. See kontrollib, kas kustutatud võtme pärimine käitub õigesti, kas suurus on pärast mitut seadistamist ja kustutamist järjepidev, ja paljusid teisi stsenaariume, mille käsitsi testimisele te ei pruugi mõelda.
Parimad praktikad ja edasijõudnute näpunäited
- Näidete andmebaas: Hypothesis on tark. Kui see leiab vea, salvestab see nurjunud näite kohalikku kausta (
.hypothesis/
). Järgmine kord, kui oma teste käivitate, mängib see selle nurjunud näite esimesena uuesti läbi, andes teile kohest tagasisidet, et viga on endiselt olemas. Kui olete vea parandanud, ei mängita näidet enam uuesti läbi. - Testi täitmise kontrollimine
@settings
abil: Saate kontrollida paljusid testi käivitamise aspekte, kasutades@settings
dekoraatorit. Saate suurendada näidete arvu, seada tähtaja, kui kaua üks näide võib joosta (lõpmatute tsüklite püüdmiseks), ja lülitada välja teatud tervisekontrolle.@settings(max_examples=500, deadline=1000) # Käivita 500 näidet, 1-sekundiline tähtaeg @given(...) ...
- Nurjumiste reprodutseerimine: Iga Hypothesise käivitus prindib seemneväärtuse (nt
@reproduce_failure('version', 'seed')
). Kui CI-server leiab vea, mida te ei suuda lokaalselt reprodutseerida, saate seda dekoraatorit kasutada koos pakutud seemnega, et sundida Hypothesist käivitama täpselt sama näidete jada. - Integreerimine CI/CD-ga: Hypothesis sobib ideaalselt igasse pideva integratsiooni konveierisse. Selle võime leida varjatud vigu enne nende tootmisse jõudmist muudab selle hindamatuks turvavõrguks.
Mõtteviisi muutus: Mõtlemine omadustes
Hypothesise kasutuselevõtt on enamat kui lihtsalt uue teegi õppimine; see on uue mõtteviisi omaksvõtmine oma koodi korrektsuse osas. Selle asemel, et küsida, "Milliseid sisendeid peaksin testima?", hakkate küsima, "Millised on selle koodi universaalsed tõed?"
Siin on mõned küsimused, mis aitavad teid omaduste tuvastamisel:
- Kas on olemas vastupidine operatsioon? (nt serialiseerimine/deserialiseerimine, krĂĽpteerimine/dekrĂĽpteerimine, tihendamine/lahtipakkimine). Omadus on see, et operatsiooni ja selle vastandoperatsiooni teostamine peaks andma tulemuseks algse sisendi.
- Kas operatsioon on idempotentne? (nt
abs(abs(x)) == abs(x)
). Funktsiooni rakendamine rohkem kui üks kord peaks andma sama tulemuse kui selle ühekordne rakendamine. - Kas on olemas teine, lihtsam viis sama tulemuse arvutamiseks? Saate testida, et teie keeruline, optimeeritud funktsioon annab sama väljundi kui lihtne, ilmselgelt õige versioon (nt oma uhke sorteerimisfunktsiooni testimine Pythoni sisseehitatud
sorted()
vastu). - Mis peaks väljundi kohta alati tõsi olema? (nt `find_prime_factors` funktsiooni väljund peaks sisaldama ainult algarve ja nende korrutis peaks võrduma sisendiga).
- Kuidas olek muutub? (Olekupõhise testimise jaoks) Milliseid invariante tuleb säilitada pärast mis tahes kehtivat operatsiooni? (nt Ostukorvis olevate toodete arv ei saa kunagi olla negatiivne).
Kokkuvõte: Uus enesekindluse tase
Omaduspõhine testimine Hypothesise abil ei asenda näitepõhist testimist. Teil on endiselt vaja spetsiifilisi, käsitsi kirjutatud teste kriitilise äriloogika ja hästi mõistetud nõuete jaoks (nt "Kasutaja riigist X peab nägema hinda Y").
Mida Hypothesis pakub, on võimas, automatiseeritud viis oma koodi käitumise uurimiseks ja ettenägematute äärmusjuhtumite eest kaitsmiseks. See toimib väsimatu partnerina, genereerides tuhandeid teste, mis on mitmekesisemad ja salakavalamad, kui ükski inimene suudaks realistlikult kirjutada. Määratledes oma koodi fundamentaalsed omadused, loote robustse spetsifikatsiooni, mille vastu Hypothesis saab testida, andes teile uue enesekindluse taseme oma tarkvaras.
Järgmine kord, kui kirjutate funktsiooni, võtke hetk, et mõelda näidetest kaugemale. Küsige endalt: "Millised on reeglid? Mis peab alati tõsi olema?" Seejärel laske Hypothesisel teha rasket tööd nende rikkumiseks. Teid üllatab, mida see leiab, ja teie kood on selle võrra parem.