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

Jednou z velkých překážek unit testů jsou závislosti. Jak otestovat třídu, je-li závislá na jiných, které nechceme našimi testy ovlivnit? Přesně o tom bude dnešní díl o testování: jak odstranit, nebo lépe – nahradit, závislosti testovaných tříd.

Seriál: Testování a tvorba testovatelného kódu v PHP (13 dílů)

  1. Testování a tvorba testovatelného kódu v PHP 13.8.2012
  2. Testování v PHP: Instalace a základy PHPUnit 27.8.2012
  3. Testování v PHP: asserty a constraints 10.9.2012
  4. Testování v PHP: praktický příklad 1.10.2012
  5. Testování v PHP: anotace 8.10.2012
  6. Testování v PHP: odstiňujeme závislosti 22.10.2012
  7. Testování v PHP: odstiňujeme závislosti II. 5.11.2012
  8. Testování v PHP: testy integrace s databází 19.11.2012
  9. Testování v PHP: testy integrace s databází II. 3.12.2012
  10. Testování v PHP: řízení běhu pomocí parametrů 7.1.2013
  11. Testování v PHP: XML konfigurace PHPUnit 21.1.2013
  12. Testování v PHP: tvorba testovatelného kódu 18.2.2013
  13. Testování v PHP: tvorba testovatelného kódu II. 11.3.2013

V dnešním (a příštím) díle seriálu o testování uzavřeme pomyslnou část o unit testech a podíváme se na poslední velkou kapitolu, která nám ještě chybí a tou je odstiňování závislostí, také známé jako mockování. O co jde?

Izolace!

Bavíme-li se o unit testování, máme vždy na mysli testování tříd v úplné izolaci. V souvislosti s testováním často narazíte na pojem „System Under Test“, zkráceně SUT. V případě unit testů je SUT každá jednotka (třída) kódu. Ostatní jednotky NESMÍ být našim testem jakkoli ovlivněny! Jinak řečeno, třída, kterou testujeme, NESMÍ využívat žádný kód kromě svého vlastního, načítat externí data apod. Pokud toto porušíme, pak už se nejedná o unit test, nýbrž o integrační. A to rozhodně není naším cílem. Alespoň prozatím.

Jak ale testovat v izolaci jednotku, která má závislosti na jiných? Např. k její instancializaci jsou nutné instance jiných tříd? Odpověď je nasnadě – musíme je nahradit nějakými dvojníky, kteří budou potřebné závislosti zastupovat. Dvojníky v takových případech budou třídy požadovaného typu, implementující požadované rozhraní. Trochu moc obecný popis, ale vše bude brzo jasnější.

Jistou analogií této problematiky by mohly být crash-testy aut. Jistě se shodneme, že by nebylo úplně vhodné tyto testy provádět se skutečnou, lidskou, posádkou. Namísto toho se využívají zástupné postavy, které věrohodně zastupují skutečnou posádku. Mají podobné vlastnosti (výška, váha), nabízí podobné rozhraní (pohyb končetinami, pohyb hlavou).

Mock, stub a ti druzí

Nahrazování skutečných objektů zástupnými kopiemi, tzv. test doubles, se obecně říká „mockování“, ale ne vždy jde o správné označení. Kromě tzv. mocků totiž existují ještě další typy zástupných objektů a jak už to bývá, jejich názvy jsou často chybně vykládány nebo zaměňovány.

Dummy

Nejprostší ze všech dvojníků. Lze si jej představit jen jako placeholder. Používá se pouze ke splnění kontraktu testované třídy – např. parametr konstruktoru. Vrátíme-li se k analogii s crash-testy, pak jako dummy si můžeme představit např. atrapu motoru. Vypadá stejně, váží stejně, ale nemusí být funkční, protože ani nemáme v plánu jej pouštět. Je přítomen jen proto, že jej testované auto vyžaduje.

Fake

Tento dvojník už je malinko sofistikovanější, ale ne o moc. Implementuje požadované rozhraní, ale často velice prostě. Jeho úkolem je opět jen splnění kontraktu, tentokrát však s podmínkou, že může dojít k volání jeho metod. Proto musí být schopen tato volání obsloužit, byť nevrací žádné výsledky.

Stub

Pod pojmem stub už si můžeme představit poměrně zdařilého dvojníka. Na rozdíl dummy a fake jsou stubs ve většině případů generovány testovacím frameworkem. Na volání dokáží reagovat předpřipravenými výsledky nebo logikou a díky tomu docela věrně napodobují svou předlohu. Kromě toho často umí „sbírat“ data o tom, které jeho metody byly volány a kolikrát, s jakými parametry apod.

Mock

Od stubs už je jen malý krůček k mocks. Nejjednodušší definice říká, že mock je stub s expektacemi. Expektace si ukážeme už za malou chvíli, zatím alespoň v kostce – jde o sadu předem definovaných pravidel, kterými např. vyžadujeme určitý počet volání nějaké metody s přesnou sadou parametrů apod. Pokud po proběhnutí test case není některá z expektací splněna, celý test case je označen za neplatný (failed).

Vytváříme mock

Jak vidíte sami, rozdíl mezi stub a mock není moc velký. Možná i proto jsou tyto dva pojmy často zaměňovány a výjimkou bohužel není ani dokumentace PHPUnit. Jako stubs jsou zde označovány mocks, které mají nastaveny expektace na „libovolně-krát“ :-) I proto si vše trochu alibisticky ulehčíme a nadále se budeme věnovat už jen mockům. Ale dost už slovíčkaření, pojďme se podívat, co nám nabízí framework PHPUnit.

Úplně základní mock můžeme vytvořit dvěma způsoby. Oba si ukážeme na jednoduchém příkladu: máme třídu Db, jejíž konstruktor vyžaduje instanci typu Logger. Naším úkolem je otestovat třídu Db a to v úplné izolaci. O volání metod skutečného loggeru nemůže být řeč, musíme jej nahradit.

interface Logger
{
    public function log($message);
}

class Db
{
    /**
     * @var Logger
     */
    protected $logger;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function execute($query)
    {
        // ... kod metody "execute" ...

        $this->logger->log("Query: " . $query);
    }
}

Prvním, a často rychlejším, způsobem jak vytvořit mock, je pomocí metody getMock. Metoda má trochu strašidelný počet parametrů, ale zpravidla využijeme jen první tři.

  • $originalClassName – název typu, který chceme nahrazovat. Držme se raději označení „typ“ než „třída“, protože mock budeme často vytvářet proti rozhraní.
  • $methods – seznam (pole) metod, které budeme překrývat. Zde pozor! Metody, které v tomto seznamu neuvedeme, nebudou mockem překryty a bude volána jejich defaultní implementace! Pokud parametr úplně vynecháte, pak žádná rodičovská implementace volána nebude.
  • $arguments – pole parametrů vyžadované konstruktorem typu.
  • $mockClassName – tímto parametrem můžeme definovat vlastní název typu (třídy) vytvořeného mocku namísto automaticky vygenerovaného (např. Mock_Db_5f032524).
  • $callOriginalConstructor – boolean příznak určující, zda má být při instancializaci mocku zavolán konstruktor mockovaného typu. Defaultní hodnota je bool(true).
  • $callOriginalClone – boolean příznak určující, zda má při volání metody __clone dojít také k zavolání stejnojmenné metody mockovaného (rodičovského) typu. Defaultní hodnota je bool(true).
  • $callAutoload – boolean příznak, zda má být použit autoloading pro natažení mockovaného typu. Defaultní hodnota je bool(true).
  • $cloneArguments – boolean příznak určující, zda mají být parametry mockovaného objektu klonovány či nikoli. Defaultní hodnota je bool(false) a příliš nedoporučuji ji měnit, pokud si skutečně nejste jisti, co činíte. K tomuto parametru se ještě vrátíme příště, dokáže napáchat hodně nepříjemností.

Pokud vás tento počet parametrů děsí, pak můžete využít ještě tzn. mock builderu, což je v podstatě jen hezčí varianta výše uvedeného, v podobě fluent interface. Namísto parametrů tak zvolíte řetězení metod jakými jsou např. setMethods, setConstructorArgs, setMockClassName a další.

Zda zvolíte první či druhý způsob je čistě na vás. Můj tip – vytváříte-li mock, který kromě názvu mockovaného typu nepotřebuje žádné jiné nastavení, zvolte první způsob:

$mock = $this->getMock("stdClass");

Vytváříte-li mock, u kterého je nutné potlačit volání konstruktoru mockovaného typu, použijte druhý způsob:

$mock = $this->getMockBuilder("stdClass")
    ->disableOriginalConstructor()->getMock();

Je to přehlednější než řada defaultních parametrů. Pojďme si pro ukázku napsat mock požadovaný našim testem a projít výsledný kód řádek po řádku:

class DbTest extends PHPUnit_Framework_TestCase
{
    public function testExecute()
    {
        $loggerMock = $this->getMock("Logger");
        $loggerMock->expects($this->once())
            ->method("log")
            ->with($this->equalTo("Query: foofoo"));

        $db = new Db($loggerMock);
        $db->execute("foofoo");
    }

}

Metoda pro vytvoření mocku už by nám měla být známá. Protože chceme vytvořit jednoduchý mock, ignorujeme všechny parametry kromě prvního – název mockovaného typu, kterým je v našem případě rozhraní Logger.

$loggerMock = $this->getMock("Logger");

Nyní jsme vytvořili v podstatě fake. Je typu Logger, ale nic konkrétního neumí. Na jakékoli volání bude odpovídat hodnotou null. Mock z něj udělají až zmíněné expektace. Jednu mu tedy přidáme, očekáváme, že v testu bude právě jednou volána jeho metoda log:

$loggerMock->expects($this->once())
        ->method("log");

Pokud nechceme definovat žádná další omezení, pak máme hotov už opravdový mock, který nám ohlídá, že v testu došlo k právě jednomu zavolání metody jménem log. Nestane-li se tak, test case selže. My ale přidáme ještě jedno omezení a tím řekneme, že očekávané volání metody log má být s jedním parametrem – řetězcem „Query: foofoo“. V testu se chystáme volat testovanou metodu execute s parametrem „foofoo“, proto právě tento řetězec.

$loggerMock->expects($this->once())
        ->method("log")
        ->with($this->equalTo("Query: foofoo"));

Expektace

Jak už jsem uváděl výše, pod pojmem expektace si můžeme představit určitou sadu pravidel, která musí být v průběhu test case splněna, jinak dojde k selhání testu. V našem prvním příkladu jsme nastavili hned dvě:

  1. bude právě jednou zavolána metoda jménem log
  2. volání metody log bude s uvedeným parametrem („Query: foofoo“)

Základní a nejpoužívanější expektací je předpokládaný (a vyžadovaný) počet volání metody mocku. Její nastavení provedeme ve dvou krocích, jako první musíme nastavit tzv. invocation matcher. Nenapadá mě vhodný český ekvivalent, proto raději zůstaneme u tohoto označení. K nastavení invocation matcher PHPUnit nabízí dvě metody:

  • expects – očekáváme volání instanční metody
  • staticExpects – očekáváme volání statické metody

Obě metody přijímají jeden parametr – instanci třídy implementují rozhraní PHPUnit_Framewor­k_MockObject_Matcher_Invo­cation. Ale není třeba se děsit, framework má několik před-definovaných matcherů, které je možné získat pomocí shortcut methods instance PHPUnit_Framework_TestCase. Pojďme se na ně podívat:

Třída Zkratka Popis
*_AnyInvokedCount $this->any() Vrací matcher, který ověřuje, zda uvedená metoda (budeme ji definovat později) byla zavolána libovolněkrát.
*_InvokedCount $this->never() Vrací matcher, který ověřuje, zda uvedená metoda nebyla nikdy zavolána.
*_InvokedAtLeastOnce $this->atLeastOnce() Vrací matcher, který ověřuje, zda uvedená metoda byla zavolána alespoň jednou.
*_InvokedCount $this->once() Vrací matcher, který ověřuje, zda uvedená metoda byla zavolána právě jednou.
*_InvokedCount $this->exactly(int $count) Vrací matcher, který ověřuje, zda uvedená metoda byla zavolána přesně tolikrát, kolik zadáme pomocí parametru $count.
*_InvokedAtIndex $this->at(int $index) Vrací matcher, který ověřuje zda uvedená metoda byla zavolána v pořadí, definovaném pomocí parametru $index (počítáno od nuly). Říká se mu také sekvenční matcher, používá se pro kontrolu pořadí volání.
(*) PHPUnit_Framework_MockObject_Matcher

Druhým krokem definice základní expektace je předání názvu metody, jejíž volání očekáváme. Toto provedeme jednoduše pomocí metody method(), viz. první příklad. Sice by to mělo být zřejmé, ale raději upozorním na fakt, že není možné mockovat metody, které jsou staticfinal nebo private!

Pokud nám nezáleží na tom, s jakými parametry bude metoda volána ani od volání metody nevyžadujeme žádnou návratovou hodnotu, pak máme nejjednodušší mock hotov. Ne vždy je to ale našim cílem, proto pojďme náš mock obohatit ještě o expektaci parametrů.

Přesně k tomu slouží metoda with() s proměnným počtem parametrů. Jednotlivými parametry jsou pak buď přímo hodnoty, které při volání očekáváme nebo instance constraints. Pokud metodě with() předáte jako parametr cokoli jiného než instanci constraint, pak framework defaultně parametr převede na constraint  PHPUnit_Frame­work_Constraint_IsEqual. Následující zápisy jsou tak ekvivalentní:

with(1, "foobar");
with($this->equalTo(1), $this->equalTo("foobar"));
with(
    new PHPUnit_Framework_Constraint_IsEqual(1),
    new PHPUnit_Framework_Constraint_IsEqual("foobar"));

Doplňujeme funkčnost

Tím jsme se dostali zpět k našemu příkladu a už bychom měli být schopni psát jednoduché mocky s expektacemi na počet volání a parametry metod. Pozorní čtenáři ale budou asi trochu protestovat, protože podle terminologie uvedené na začátku tohoto dílu, jsme zatím nevytvářeli mock, ale spíše něco jako „fake s expektacemi“. Správný mock by přece měl předstírat funkčnost své předlohy. Našemu mocku chybí přesně ta část, které se říká stub.

Pod pojmem stub je v PHPUnit myšlena třída implementující rozhraní PHPUnit_Framewor­k_MockObject_Stub a framework nám opět nabízí několik prefabrikovaných. Stejně jako v případě invocation matchers, i zde můžeme využít zkratky v podobě metod instance PHPUnit_Framework_TestCase:

Třída Zkratka Popis
*_Return $this->returnValue($value); Při volání metody vrátí zadanou hodnotu. Hodnotou může být cokoli, framework ji nijak nemodifikuje.
*_ReturnArgument $this->returnArgument($argumentIndex); Při volání metody vrátí parametr určený zadaným indexem (počítáno od nuly). Seznam parametrů je určen metodou with().
*_ReturnCallback $this->returnCallback($callback); Při volání metody zavolá zadaný callback s parametry, definovanými v metodě with().
*_ReturnSelf $this->returnSelf(); Při volání metody vrátí sám sebe (instanci mocku).
*_ReturnValueMap $this->returnValueMap(array $valueMap); Při volání metody vrací předem definovanou mapu hodnot, což jsou pole nesoucí jak vstupní parametry, tak návratovou hodnotu.
*_ConsecutiveCalls $this->onConsecutiveCalls([args]); Při opakovaných voláních metody jsou postupně (sekvenčně) vraceny zadané hodnoty. Pokud je vracená hodnota instancí implementující PHPUnit_Framework_MockObject_Stub, pak je nejprve zavolána její metoda invoke.
*_Exception $this->throwException(Exception $exception); Při volání metody vyvolá zadanou výjimku.
(*) PHPUnit_Framework_MockObject_Stub

V přehledu se objevila celá řada neznámých pojmů, pojďme si vše ukázat na příkladech.

returnValue

Tento stub jsme už použili v prvním příkladu, jeho chování je přímočaré – jednoduše vrátí zadanou hodnotu.

returnArgument

Stub podobný předchozímu, jen při volání mockované metody, namísto konkrétní hodnoty, vrátí některý z jejích parametrů, určený pořadím (počítáno od nuly).

class ReturnArgumentTest extends PHPUnit_Framework_TestCase
{
    public function testReturnArgument()
    {
        $mock = $this->getMock("stdClass", array("foo"));
        $mock->expects($this->once())
            ->method("foo")
            ->with("bar", "baz")
            ->will($this->returnArgument(0));

        $this->assertEquals("bar", $mock->foo("bar", "baz"));
    }
}

returnCallback

Pokud potřebujeme pomocí stub simulovat nějakou logiku, např. výpočet, můžeme s úspěchem použít tento stub. Jako callback lze předat cokoli, co splňuje type hint callable (viz.http://www.php.net/ma­nual/en/language.types.ca­llable.php).

class ReturnCallbackTest extends PHPUnit_Framework_TestCase
{
    public function testReturnCallback()
    {
        $mock = $this->getMock("stdClass", array("calc"));
        $mock->expects($this->exactly(3))
            ->method("calc")
            ->will($this->returnCallback(
                function($x, $y) {
                    return $x + $y;
                }
            )
        );

        $this->assertSame(3, $mock->calc(1, 2));
        $this->assertSame(0, $mock->calc(-2, 2));
        $this->assertSame(0, $mock->calc(0, 0));
    }

    public function testReturnForeignCallback()
    {
        $mock = $this->getMock("stdClass", array("calc"));
        $mock->expects($this->exactly(3))
            ->method("calc")
            ->will($this->returnCallback(array($this, "calcCallback")));

        $this->assertSame(1, $mock->calc(0, 1));
        $this->assertSame(2, $mock->calc(-2, 4));
        $this->assertSame(3, $mock->calc(3, 0));
    }

    public function calcCallback($x, $y)
    {
        return $x + $y;
    }
}

returnSelf

Jak bylo uvedeno v přehledu – tento stub vrací instanci mocku. K čemu je to dobré? Např. pro testování fluent interface.

class ReturnSelfTest extends PHPUnit_Framework_TestCase
{
    public function testReturnSelf()
    {
        $mock = $this->getMock("stdClass", array("foo", "bar"));
        $mock->expects($this->exactly(2))
            ->method("foo")
            ->will($this->returnSelf());

        $mock->expects($this->once())
            ->method("bar")
            ->will($this->returnSelf());

        $this->assertSame($mock, $mock->foo());
        $this->assertSame($mock, $mock->foo()->bar());
    }
}

returnValueMap

V přehledu bylo předesláno, že stub vrací cosi jako mapu hodnot, která obsahuje jak vstupní parametry, tak návratovou hodnotu. Jakkoli to zní krkolomně, skrývá se za tím prostá myšlenka. Pokud si vzpomínáte, tak v minulém díle, věnovanému anotacím, jsme si ukazovali anotaci jménem @dataProvider. Toto je v podstatě její obdoba. Předem si definujeme pole, jehož posledním prvkem bude návratová hodnota metody a všechny prvky před ním budou metodě předány jako parametry. Ukázka vše objasní:

$map = array(
    array(1, 2, 3),
    array(2, 2, 4),
    array(5, 5, 10)
);

Zavoláme-li mockovanou metodu s parametry: 1 a 2, pak návratová hodnota bude: 3. Zavoláme-li ji s parametry: 2 a 2, návratová hodnota bude: 4, a tak dále… Co se stane v případě, že budeme volat metodu s kombinací parametrů, která v mapě není? Jednoduše vrátí null.

class ReturnValueMapTest extends PHPUnit_Framework_TestCase
{
    public function testReturnValueMap()
    {
        $map = array(
            array(1, 2, 3),
            array(2, 2, 4),
            array(5, 5, 10));

        $mock = $this->getMock("stdClass", array("calc"));
        $mock->expects($this->any())
            ->method("calc")
            ->will($this->returnValueMap($map));

        $this->assertSame(10, $mock->calc(5, 5));
        $this->assertSame(4, $mock->calc(2, 2));


        $this->assertNull($mock->calc(5, 6));
    }
}

onConsecutiveCalls

Tento stub je jednodušší variantou předchozího. Volání mockované metody postupně vrací zadané hodnoty. Opět se nabízí otázka – co bude vráceno, pokud počet volání překročí definovaný počet návratových hodnot? Stejně jako v předchozím případě – je vráceno null.

class OnConsecutiveCallsTest extends PHPUnit_Framework_TestCase
{
    public function testOnConsecutiveCalls()
    {
        $mock = $this->getMock("stdClass", array("getValue"));
        $mock->expects($this->exactly(4))
            ->method("getValue")
            ->will($this->onConsecutiveCalls(5, 3, 1));

        $this->assertSame(5, $mock->getValue());
        $this->assertSame(3, $mock->getValue());
        $this->assertSame(1, $mock->getValue());
        $this->assertSame(null, $mock->getValue());
    }
}

throwException

Poslední z popisovaných stubs, má jednoduchý úkol – při volání mockované metody vyvolat výjimku. V ukázce použijeme anotaci známou z minulého dílu – @expectedException.

class ThrowExceptionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testThrowException()
    {
        $exception = new InvalidArgumentException();

        $mock = $this->getMock("stdClass", array("foo"));
        $mock->expects($this->once())
            ->method("foo")
            ->will($this->throwException($exception));

        $mock->foo();
    }
}

Sekvenční matcher „at“

Téměř všechny stuby uvedené v ukázkách měly za úkol řízení návratových hodnot mockovaných metod. Občas se ale můžete setkat s následujícím problémem – nezáleží úplně na návratové hodnotě, ale je nutné aby byla mockovaná metoda poprvé zavolána s jednou, přesnou, sadou parametrů, podruhé s jinou, opět přesnou, sadou parametrů. Dost možná vás napadne řešení podobné tomuto:

class MatcherAtTest extends PHPUnit_Framework_TestCase
{
    public function testMatcherAt()
    {
        $mock = $this->getMock("stdClass", array("foo"));
        $mock->expects($this->once())
            ->method("foo")
            ->with(1, 2, 3);

        $mock->expects($this->once())
            ->method("foo")
            ->with(4, 5, 6);

        $mock->foo(1, 2, 3);
        $mock->foo(4, 5, 6);
    }
}

Poprvé nastavíme právě jedno volání metody s první sadou parametrů, podruhé opět právě jedno volání s druhou sadou parametrů. Tento zápis ale bohužel fungovat nebude, protože druhým nastavením si pouze přepíšete to první a test nám selže s hlášením, že namísto očekávané hodnoty prvního parametru „4“ byla metoda volána s prvním parametrem rovným „1“.

Správným řešením je použít sekvenční matcher at(), kterým můžeme definovat přesnou posloupnost volání mockované metody.

class MatcherAtTest extends PHPUnit_Framework_TestCase
{
    public function testMatcherAt()
    {
        $mock = $this->getMock("stdClass", array("foo"));
        $mock->expects($this->at(0))
            ->method("foo")
            ->with(1, 2, 3);

        $mock->expects($this->at(1))
            ->method("foo")
            ->with(4, 5, 6);

        $mock->foo(1, 2, 3);
        $mock->foo(4, 5, 6);
    }
}

Jako vždy – všechny ukázky, jak z tohoto, tak i z předchozích dílů seriálu najdete na mém Githubu:

https://github.com/josefzamrzla/serial-testovani-v-php

Příště

Příště se podíváme na zbytek problematiky odstiňování závislostí. Krátce probereme mockování webservices a file systému a vše vyzkoušíme na praktickém příkladu.

Josef Zamrzla pracuje jako nezávislý vývojář. Před tím působil coby software development engineer ve společnosti Skype, programátor ve společnosti LMC s.r.o. (provozovatel pracovních portálů www.jobs.cz a www.prace.cz) nebo teamleader ve společnosti Kasa.cz 

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 29

Přehled komentářů

arron Testování pořadí volání metod
Josef Zamrzla Re: Testování pořadí volání metod
arron Re: Testování pořadí volání metod
michal Tleskám
náhodný čtenář Re: Testování v PHP: odstiňujeme závislosti
Josef Zamrzla Re: Testování v PHP: odstiňujeme závislosti
Martin Hassman Re: Testování v PHP: odstiňujeme závislosti
náhodný čtenář Re: Testování v PHP: odstiňujeme závislosti
Clary Re: Testování v PHP: odstiňujeme závislosti
Josef Zamrzla Re: Testování v PHP: odstiňujeme závislosti
Clary Re: Testování v PHP: odstiňujeme závislosti
Mystik_7 Re: Testování v PHP: odstiňujeme závislosti
arron Re: Testování v PHP: odstiňujeme závislosti
Honza Použití mocku
arron Re: Použití mocku
5o Re: Použití mocku
Josef Zamrzla Re: Použití mocku
arron Re: Použití mocku
Josef Zamrzla Re: Použití mocku
Clary Re: Použití mocku
Michal Re: Použití mocku
Josef Zamrzla Re: Použití mocku
arron Re: Použití mocku
Honza Re: Použití mocku
Jirka Mock na jednu metodu testovaného objektu
Josef Zamrzla Re: Mock na jednu metodu testovaného objektu
Jirka Re: Mock na jednu metodu testovaného objektu
Podbi Užitečný článek, zajímavá diskuse
Jan Prachař Mock vs. stub
Zdroj: https://www.zdrojak.cz/?p=3731