neděle 12. února 2012

Jak nepsat jednotkové testy

... aneb pozor na lháře, křiklouna a místního hrdinu

Uvádím seznam nejfrekventovanějších antivzorů (špatných technik) pro psaní jednotkových testů. Vyvarujte se jich a Vaše testy budou v dobré kondici ;-)

  • Lhář (The Liar). Jednotkový test, který prochází pro každý testovací případ. Optimismus nás však přejde, pokud se podíváme blíže na implementaci testu a zjistíme, že ve skutečnosti požadovanou vlastnost netestuje.

  • Nadměrná příprava (Excessive Setup). Test, který potřebuje rozsáhlou přípravu ještě před samotným spuštěním testovaného kódu. Stovky řádků a velké množství objektů, které test vyžaduje, způsobí, že je obtížné ověřit testovanou funkcionalitu. Test může selhat z mnoha jiných příčin, než z vlastní chyby v testovaném kódu.

  • Obr (The Giant). Jednotkový test, který pokrývá velké množství testovacích případů a je v rozsahu stovek až tisíců řádků. Přestože validně testuje testovaný kód, jedná se zřejmě o jednotkový test nad božským objektem (God object). Tedy chybný návrh třídy, která v sobě slučuje více zodpovědností.

  • Imitátorna (The Mockery). Používání mock objektů je v mnoha případech dobré a šikovné. V některých případech však mohou vývojáři ztratit kontrolu sami nad sebou a nadměrným používáním mock, fake a stub objektů potlačit vlastní testovanou funkcionalitu. Dochází pak spíše k testování dat, které vracejí jednotlivé mock objekty. Definice mock objektů je navíc velmi křehká a může být snadno rozbita a chybně způsobí selhání testu. To je samo o sobě proti principům TDD. Příliš mnoho závislostí a komplikované vazby na další třídy může indikovat testování božského objektu. V takovém případě by měla být testovaná třída refaktorována nebo by mělo být jedno volání nahrazeno sekvencí více volání metod s menším rozsahem funkcionality. Každé dílčí volání by pak mohlo být otestováno samostatně a není nutné znovu testovat celou sekvenci jako celek.

  • Inspektor (The Inspector). Inspektor je jednotkový test, který ví příliš mnoho o vnitřní struktuře testovaného kódu. Je napsán na míru z důvodu 100% pokrytí kódu. V případě, že je testovaný kód refaktorován a přestože se jeho validita nemění, tzn. měl by procházet, začne test neprocházet. To vynutí současnou změnu i v jednotkovém testu.

  • Velkorysé zbytky (Generous Leftovers). Jeden jednotkový test vytvoří data, která jsou někde persistována (uchována). Jiný test tato data využívá pro svoje vlastní (původnímu testu neznámé) účely. Pokud je takový "test-generátor" spuštěn později nebo pouze částečně, dochází chybně k selhání v testu, který je na něm závislý.

  • Místní hrdina (The Local Hero). Test je napsaný tak, že obsahuje závislosti na prostředí, ve kterém byl vyvinut. V případě, že je spuštěn v těchto podmínkách, pak v pořádku prochází. Pokud se však test spustí v jiném prostředí, selže.

  • Hnidopich (The Nitpicker). Test, který prověřuje kompletní výstup, přestože významná je pouze část vrácené informace. Pokud se změní nevýznamová část vrácené informace, začne test chybně selhávat. Takové testy jsou typické při testování webových aplikací.

  • Tajemný lovec (The Secret Catcher). Na první pohled takový test vypadá, že nic netestuje, neboť nemá žádné asserty. Ovšem v tomto případě platí rčení, že "ďábel je ukryt v detailech". Test předpokládá, že v případě selhání vyvolá testovaný kód výjimku, kterou zachytí a zpracuje testovací framework. Řešením by mohlo být odchycení výjimky do proměnné určitého typu a porovnání na null hodnotu. Nebo můžete použít atribut ExpectedException.

  • Ulejvák (The Dodger). Jednotkový test, který testuje několik méně podstatných aspektů testovaného kódu a vyhýbá se otestování chování zásadního. Obvykle z důvodu vyšší složitosti takového otestování.

  • Křikloun (The Loudmouth). Jednotkový test nebo sada testovacích případů, které posílají na konzoli velké množství testovacích, ladících nebo protokolovacích zpráv, i v případě, že testy procházejí. Jedná se obvykle o pozůstatky ručního ladění. Tyto nevýznamné zprávy znepřehledňují výsledký protokol o výsledku spuštěných testů.

  • Nenasytný lovec (The Greedy Catcher). Jednotkový test, který zachytí výjimku a "polkne" ji včetně trasování zásobníku. Tuto výjimku někdy nahradí informačně méně hodnotnou zprávou. Někdy dokonce chybu pouze zaloguje a test nechá projít.

  • Řadič (The Sequencer). Jednotkový test, jehož procházení je závisle na určitém pořadí assertů, u kterých by na pořadí záležet nemělo.

  • Skrytá závislost (Hidden Dependency). Hodně podobný "Místnímu hrdinovi". Test přepokládá, že před jeho spuštěním jsou připravena data, na kterých je závislý. Pokud tomu tak není, test selže. Vývojář obdrží omezenou informaci o problému a je nucen projít velké množství kódu a zjistit problém se závislými daty. Například starší .dll knihovny mohou být závislé na ini souboru, který řídí jejich chování. Je obvykle složité dopátrat se vlastní příčiny selhání testu a této skryté závisloti.

  • Výčet (The Enumerator). Jednotkový test s testovacími případy, které se jmenují podobně. Např. TestMethod1(), TestMethod2(), ... V tomto případě není možné z názvů testovacích případů určit jejich význam. V případě selhání je vývojář nucen procházet přímo zdrojový kód testu a snažit se pochopit jeho význam.

  • Cizinec (The Stranger). Testovací případ, který nepatří do jednotkového testu. Ve skutečnosti testuje jiný typ objektu, který je využíván a vrácen testovaným objektem.

  • Kazatel operačního systému (The Operating System Evangelist). Jednotkový test, který se opírá o specifické prostředí nebo vlastnosti operačního systému. Příkladem může být assert v testovacím případě, který ověřuje sekvenci znaků pro nový řádek a předpokládá Windows konvenci. Takový test selže při spuštní na Linuxu.

  • Úspěch zaručen (Success Against All Odds). Test, který byl napsán nejdříve tak, aby prošel, místo aby selhal. Naneštěstí pak takový testovací případ prochází i v situacích, kdy by měl selhat.

  • Jízda zadarmo (The Free Ride). Namísto vytvoření nové metody testovacího případu pro otestování další vlastnosti nebo funkcionality, se přidá pouze nový assert k již existujícím.

  • Jedinečný (The One). Kombinace několika vzorů, zejména "Jízdy zadarmo" a "Obra". Test obsahuje jeden testovací případ, který testuje celou sadu funkcionalit testovaného objektu. Obvyklým ukazatelem je to, že testovací metoda se jmenuje stejně jako jednotkový test.

  • Vykukující kocour (The Peeping Tom). Test, který skrze sdílené zdroje vidí na výsledná data jiných testů. Na základě těchto dat může test selhat, přestože testovaný systém je pro testovací případ validní. Toto se běžně stávalo ve FitNesse, kde se používaly statické členské proměnné pro uchování kolekcí, které nebyly korektně vyčištěny po proběhnutí testu. Tento problém se objevoval neočekávaně při některých bězích testů. Vzor známý také jako "Nezvaní hosté" (The Uninvited Guests).

  • Pomalé dloubnutí (The Slow Poke). Jednotkový test, který běží neúnosně pomalu. Když jej vývojář spustí, může si zajít do koupelny nebo zakouřit. V nejhorším případě jej může spustit na konci šichty před odchodem domů.

  • Štastná cesta (Happy Path). Test probíhá pouze po hladké, bezproblémové cestě. Netestuje hraniční hodnoty a výjimky.

  • Podřadní občané (Second Class Citizens). Testovací kód není tak dobře refaktorovaný jako testovaný kód, obsahuje duplicity a je špatně udržovatelný.

  • Spoutaní řetězem (Chain Gang). Dvojice testů, které musí být spuštěné v určitém pořadí. Například jeden test nastaví globální stav systému (globální proměnná, databázová data) a druhý test je na tomto stavu závislý. Např. u databázových testů se může stát, že pokud není provádění uzavřeno ve chráněném bloku a test selže, není po testu korektně uklizeno.

  • Bezejmenný test (The Test With No Name). Test, který byl vytvořen, aby reprodukoval nalezenou chybu a jeho autor nepovažoval za důležité vymyslet mu významové jméno. Namísto posílení (rozšíření) existujícího testu, je vytvořen nový test s názvem TestProChybu123. Po dvou letech, kdy tento test začne selhávat, musíte do systému pro sledování chyb a hledáte Chybu123, abyste pochopili účel tohoto testu.

  • Spáč (The Sleeper). Test, který selže v určitou dobu nebo po určitém datu. Jedná se často o nekorektní kontrolu hraničních hodnot při testování kódu, který pracuje s objekty typu Date nebo Calendar. Problémový může být také běh o půlnoci. Chyba je na straně kódu testu.

Informační zdroje

Článek jsem sestavil ze dvou níže uvedených zdrojů a dokořenil jej vlastními vsuvkami. Může se stát, že se mi nepodařilo trefit ideální český ekvivalent pro název některého antivzoru. Navrhněte lepší, rád upravím.

3 komentáře:

  1. Děkuju za super článek.

    Mám tam pár maličkostí, kdy některé situace nemusí být považovány za anti-pattern.

    Imitátorna - v určitých situacích to jinak, než spoustou mocků, udělat prostě nejde. Typicky controllery pracují se spoustou objektů (request, response, flashMessenger, modely, view, ACL) a přitom to žádné god objekty nejsou (u jiných možná, u mě určitě ne). Tam nezbývá než buď nasadit až akceptační testy, nebo se smířit se spoustou mocků.

    Inspektor - inspektor je ukázkou typického white-box testování. Zastánci black-box (bez znalosti vnitřní implementace) označí za anti-pattern to, co zastánci white-box za pattern a stejně tak zastánci budou definovat nějaký pattern dummy-blind-test, kdy test testuje a nic o objektu neví. Sám jsem zastáncem black-box (když člověk dělá TDD, ani jinak nemůže), ale fanatismus mě už opustil. Občas, když se v nějaké třídě chyby často opakují, přepíšu její testy se znalostí vnitřní implementace (typický Inspektor) a díky tomu danou třídu otestuju mnohem líp a problémy obvykle odstraním.

    Podřadní občané - jen bych byl opatrný na považování duplicit v testech za něco špatného. Test je "příklad použití", jako takový slouží i jako dokumentace a "vypráví příběh objektu". Tak je vhodné, aby bylo možné ten příklad v testu snadno sledovat (tzn. nedelegovat slepě do jiných objektů, pokud se nějaký kód opakuje ve více testech). Všude jinde se samozřejmě duplicitnímu kódu bráním.

    Jinak ještě jednou díky, ostatní kroky opravdu považuju za anti-patterny, které se ale naštěstí moc nedějí.

    Za sebe bych přidal ještě anipattern "svázání se systémem", kdy lidé do testů píšou přímo cesty typu "C:\web\tests\testx.txt".

    OdpovědětVymazat
    Odpovědi
    1. Děkuji za pěkný komentář a doplnění problematiky.

      Líbí se mi Váš nedogmatický přístup. Vzory a antivzory jsou pokusy vnést pravidla a řád do složitého světa vývoje software. Míra dodržování těchto pravidel souvisí s úrovní zkušeností z praxe. Pro nováčky to mohou být dogmata. Zkušený vývojář ví, že pro konečný úspěch projektu je někdy potřeba z některých pravidel slevit. Jeho vnímání vzorů a anitivzorů je flexibilnější.

      Vymazat
    2. Nedávno jsem narazil na hezký citát na toto téma:
      Experience teaches patterns that tend to work. Not to be confused with "the only patterns that work" or "patterns that always work". -- Kent Beck

      Každé dogma je z principu špatné. Všechny patterny/anti-patterny jsou jen zjednodušené příklady řešení, která obvykle fungují. Nikdy je nesmíme zobecnit na všechny případy (a jejich různé kontexty), protože ve všech případech prostě fungovat nemohou - množina různých kontextů použití příliš velká na to aby se dala redukovat na jednoduchý vzor.

      Vymazat