29 komentářů k článku Testování v PHP: odstiňujeme závislosti:

  1. arron

    Testování pořadí volání metod

    Super článek :-)

    Celé je to fakt hezky vymakané, nicméně pokud chci udělat opravdu dobrý unit test, tak musím opravdu testovat pořadí volání jednotlivých závislostí. Tak jak je to popisováno v článku, tak se dá, docela pohodlně, testovat pořadí volání v rámci jedné závislosti. Jak to ale řešit, když se bude volat víc závislostí a budu testovat i pořadí volání mezi nimi (tzn. nejdřív se zavolá jedna závislost, pak druhá a pak zase ta první a tohle pořadí potřebuji otestovat)? Je možné tohle s pomocí phpunitu vyřešit? Nebo je na to potřeba si podporu přidat?

    Díky moc

    1. Josef ZamrzlaAutor příspěvku

      Re: Testování pořadí volání metod

      IMHO PHPUnit nic takového nenabízí. Expektace je možné definovat jen každému mocku zvlášť.

      1. arron

        Re: Testování pořadí volání metod

        To je přesně, co jsem jsi myslel :-) říkal jsem si jenom, jestli jsem náhodou nějakou techniku nepřehlédnul :-)
        Díky moc

  2. náhodný čtenář

    Re: Testování v PHP: odstiňujeme závislosti

    Proč nepoužít rovnou integrační testování namísto složitých mocků?

    1. Josef ZamrzlaAutor příspěvku

      Re: Testování v PHP: odstiňujeme závislosti

      Právě proto, že chceme testovat jednotkově a ne integračně. Vrátím-li se k analogii s crash testy, je to stejné jako byste se ptal, proč nepoužívat lidskou osádku namísto zdlouhavě vyráběných dummies.

      1. Martin Hassman

        Re: Testování v PHP: odstiňujeme závislosti

        Já bych ještě argumentoval filosofickým rozdílem:
        * Unit testy – ověřuji, zda jsem kód napsat správně (zda dělá, co má, zadané vstupy funkcí vrací očekávané výstupy).
        * Integrační testy – ověřuji, zda ty správně napsané kusy kódu spolu správně komunikují.

      2. náhodný čtenář

        Re: Testování v PHP: odstiňujeme závislosti

        Promiňte, ale to je zcela uhozený příklad. Lidskou osádku nepoužíváme proto, že by ti lidé utrpěli zranění, případně zemřeli. Co zemře, když budu třeba načítat skutečná data z databáze namísto mockovaných dat?

        1. Josef ZamrzlaAutor příspěvku

          Re: Testování v PHP: odstiňujeme závislosti

          To byla pouze analogie ;-) Jak píší kolegové níže, jde hlavně o přesnou identifikaci problému. Integrační testování se používá pro testování celku, zatímco my se zabýváme detailním testováním implementace.

    2. Clary

      Re: Testování v PHP: odstiňujeme závislosti

      Cílem je identifikovat místo vzniku chyby. V integračním testu, kde je mnoho závislostí, se velmi špatně hledá na které konkrétní věci test spadnul.

      Také pokud jsem napsal třídu, která je jedntokově testovatelná (tj. má vyřešené závsislosti a splňuje některé další atributy) můžu si být zase o něco jist, že je tato třída napsána dobře (v rámci čistého objektového návrhu)

    3. Mystik_7

      Re: Testování v PHP: odstiňujeme závislosti

      Integrační test pomůže odhalit, že někde v systému je chyba. Jednotkový test pomůže odhalit, kde ta chyba je.

      Další rozdíl je, že jednotkové testy jsou rychlé a je je tedy možné spouštět často – mnohdy se doporučuje je spouštět po každém commitu.

      Integrační testy jsou obvykle řádově pomalejší a proto se spouštějí méně často. Například jednou za hodinu.

      Cílem jednotkových testů je pak co nejrychleji odhalit možný problém. Cílem integračních testů je pokrýt oblasti, které jednotkové testy vynechaly a ujistit se, že systém funguje jako celek.

    4. arron

      Re: Testování v PHP: odstiňujeme závislosti

      Integrační testy samozřejmě použít můžete, otázka je, jestli z nich budete mít takový užitek :-)

      Integrační testy jsou výborná věc, otestují celý systém, jestli spolu jednotlivé komponenty dobře spolupracují a podobně. V to je jejich velká síla. A zároveň slabina…pokud nastane v integračních testech chyba, nemusí být na první pohled jasné, co přesne chybu způsobilo. Je potřeba se hrabat v různých log souborech a s trochou smůly začít eliminovat jednotlivé nápady, kde mohla chyba vzniknout…což je ve většině případů pomalé až velmi pomalé…

      Trochu jiná nastává situace, kdy můžeme jednotlivým komponentám „věřit“, že opravdu dělají to, co bylo zamýšleno, aby dělaly. Tím se zásadně zužuje seznam možností selhání.

      A přesně v tuhle chvíli nastupují unit testy. Jsou (mají být!) velmi rychlé a protože testují jenom velmi omezenou část kódu, takže příčina chyby se daleko lépe lokalizuje. Navíc jí programátor zachytí velmi rychle, protože spouští unit testy před každým commitem, popřípadě je CI prostředí spoučtí automaticky a chyba se tak objeví opravdu v řádu minut. Mimojiné i proto, že programátor mám problém ještě v hlavě, dokáže zpravidla opravdu rychle odhalit jeho příčinu.

      Tzn. do integračních testů by měl vstupovat o unittestovaný kód o kterém můžeme tvrdit, že každá jeho část dělá to, co bylo zamýšleno a dále testujeme opravdu už jenom integraci jako takovou. Jde to samozřejmě dělat včechno dohromady v integračních testech a ačkoliv si dovedu představit prostředí, kde by se to vyplatilo, obecně se to považuje za pomalé a méně efektivní.

  3. Honza

    Použití mocku

    Asi jsem něco přehlédl, ale nenašel jsem v článku, jak donutím testovaný objekt použít mock místo skutečné instance mockované třídy, pokud si ji například vytváří v konstruktoru nebo se k ní dostane nějak jinak?

    1. arron

      Re: Použití mocku

      Nic jste nepřehlédl. Ono to totiž nejde.

      Pokud je v konstruktoru něco jako new SomeClass(); tak se s tím nedá nic dělat. Je to chyba návrhu a je potřeba návrh upravit, aby třída byla testovatelná. A to je přesně ten bod, kdy vám unit testy pomohou zlepšít návrh a de facto vás „donutí“ začít používat některé „správné praktiky“ (zde například DI) :-)

      Pokud vytváříte objekt přes nějakou factory, tak potom záleží, jestli tuto factory dostává objekt zvenku. Pokud ano, uděláte mock factory a je v podstatě hotovo :-) (pod tím si představuju to „nějak jinak“)

        1. Josef ZamrzlaAutor příspěvku

          Re: Použití mocku

          Ano, to je jedna z hardcore možností. Bohužel ale řeší důsledek, ne příčinu. Podobné techniky je dobré používat jen pro legacy kód, který se chystáme refaktorovat.

        2. arron

          Re: Použití mocku

          Heleme se, o tom jsem nemél ani tušení :-) V tom php se fakt dá naprasit fakt uplně cokoliv, jak tak na to koukám :-D

    2. Josef ZamrzlaAutor příspěvku

      Re: Použití mocku

      Honzo, vydržte do druhé části seriálu, kde se těmto problémům budeme věnovat. Existují samozřejmě techniky, jak „podstrčit“ mock třídě, která si vytváří tvrdou závislost (např. instancializace v konstruktoru), ale samozřejmě platí – aby byla třída mockovatelná, tak musí být testovatelná. Pro tuto chvíli se podívejte na tyto tři zápisy:

      class Db {
          public function __construct(Logger $logger) {
              $this->logger = $logger;
          }
      
          public function execute() {
              $this->logger->log("foofoo");
          }
      }
      class Db {
          public function __construct() {
              $this->logger = new Logger();
          }
      
          public function execute() {
              $this->logger->log("foofoo");
          }
      }
      class Db {
          public function __construct() {}
      
          public function execute() {
              $logger = new Logger();
              $logger->log("foofoo");
          }
      }

      Zatímco v prvním případě nebudete mít žádný problém vnutit třídě Db mock, ve druhém už to nebude vůbec jednoduché (budete nucen k nějakému „hackování“ třídy pomocí reflexe), ve třetím případě prakticky (běžnými postupy) nemožné.
      Vždy byste se měl snažit aby se třída otevřeně hlásila ke svým závislostem a tyto příjímala namísto jejich in-line vytváření. Tento postup je znám jako Dependency injection.

      1. Clary

        Re: Použití mocku

        U nás ve firmě se ještě s oblibou používají globální „singletony“ :-/ (jenom se tváří jako singleton, konstruktory jsou veřejné) a to asi tímto způsobem:

        class BazModel
        {
        function foo()
        {
        Transaction::ge­tInstance()->begin();
        $something = BazModel::getIn­stance()->getSomething();
        BarModel::getIn­stance()->doSomething($so­mething)
        Transaction::ge­tInstance()->commit();
        }
        }

        1. Michal

          Re: Použití mocku

          Krasa tak to muzete rovnou zahodit a napsat znovu :-) .
          Doporucuju zaroven prejit ze svn na git pokud ho jeste nepouzivate :-))

        2. arron

          Re: Použití mocku

          Tohle je sice opravdu šílený, ale zase se to docela jednoduše (a poměrně bezpečně) dá rychle refaktorovat. Ono jenom zavření získávání instance do metod ten kód dost zlepší a přitom se to dá udělat skoro automaticky. A od toho už je jenom krůček k tomu, aby se tam ty instance odněkud injectovali.

          Takže rozhodně nemazat, ale postupně refaktorovat :-) Veřte, že výsledek bude lepší než to psát celé znovu.

      2. Honza

        Re: Použití mocku

        Díky za odpověď, myslel jsem si, že to tak nějak bude, ale vidět to takhle rozepsané rozhodně dost pomůže.

  4. Jirka

    Mock na jednu metodu testovaného objektu

    Měl bych dotaz. Mějmě třídu, která má dvě metody. Řekněme metodu A a metodu B. Metoda A provádí nějaký výpočet a jedním ze vstupů tohoto výpočtu je výsledek metody B.

    Př.

    public function A() {
    return 2*$this->B();
    }

    A teď si představme, že chceme otestovat funkčnost metody A, ale zároveň do toho testu nezahrnout testování funkčnosti metody B. V podstatě něco jako udělat Mock jen na některých metodách testovaného objektu. Prostě tak aby A fungovala normálně, zatímco B vrácela „podvržené výsledky“.

    Lze tohoto chování v PHPUnit nějakým způsobem docílit, nebo to prostě nelze a nebo je to uhozená myšlenka jdoucí proti nějakému pravidlu ať už OOP nebo jednotkového testování?

    Jedno z možných řešení, na které jsem narazil je vytvořit odvozenou třídu jen pro testování. Tato testovací třída by metodu A podědila a metodu B přeimplementovala podle požadavku testu. Toto řešení mi ale příjde neelegantní, něco jako hrabat se levou rukou za pravým uchem (příp. pro praváky obráceně).

    Moc děkuji za nakopnutí příp. za vysvětlení proč to nelze. Jirka

    1. Josef ZamrzlaAutor příspěvku

      Re: Mock na jednu metodu testovaného objektu

      Je to možné – do seznamu mockovaných metod zařadíš jen tu, kterou chceš překrýt (její chování mockuješ). Mrkni se na https://github.com/josefzamrzla/serial-testovani-v-php/tree/master/06-mocks/partialMock přidal jsem příklad tam. Testuji třídu Worker, která obsahuje metodu „work“, jejíž implementaci pro potřeby testu překryji. Metodu „checkResults“, která předchozí metodu volá, nezahrnu do seznamu mockovaných metod, takže bude volána její původní implementace.

  5. Podbi

    Užitečný článek, zajímavá diskuse

    Další kvalitní článek o testování a k tomu zatím i dost zajímavá a podnětná diskuse. Jen tak dál! Těším se na další díl :).

Napsat komentář

Tato diskuse je již příliš stará, pravděpodobně již vám nikdo neodpoví. Pokud se chcete na něco zeptat, použijte diskusní server Devel.cz

Zdroj: https://www.zdrojak.cz/?p=3731