TDD_I_01: Peníze v několika měnách

Začneme objektem, který Ward vytvořil pro systém WyCash: penězi v několika měnách (viz předmluva). Předpokládejme, že máme takovouto sestavu:

SpolečnostAkcieCenaCelkem
IBM10002525000
GE40010040000
Celkem65000

Máme-li vytvořit sestavu pro více měn, musíme přidat měny:

SpolečnostAkcieCenaCelkem
IBM100025 USD25000 USD
Novartis400150 CHF60000 CHF
Celkem65000 USD

Také musíme určit kurz:

ZNaKurz
CHFUSD1,5

$5 + 10 CHF = $10 je-li kurz 2:1

$5 * 2 = $10

Jaké chování musíme zajistit k dosažení požadované sestavy? Nebo jinak, jaká sada úspěšných testů prokáže přítomnost kódu, o kterém budeme moci prohlásit, že počítá sestavu správně?

Vytvoříme si plán, který nám bude stále připomínat, co musíme udělat, udrží naši pozornost a řekne nám, kdy máme hotovo. Ldyž začneme pracovat na nějakém úkolu, vyznačíme jej tučně takto. Když úkol dokončíme, přeškrtneme jej, takto. Napadne-li nás nějaký nový test, přidáme jej do seznamu.

Jak vidíte na plánu z předchozí strany, budeme se nejprve věnovat násobení. Jaký objekt tedy potřebujeme nejdříve? Záludná otázka. Nezačínáme s objekty, ale s testy (neustále si to musím připomínat, proto se budu tvářit, že jste stejně nedovtipní jako já).

Takže znovu. Jaký test potřebujeme nejdříve? Podíváme-li se na náš seznam, zjistíme, že první test vypadá složitě. Začneme od Adama. Násobení jak těžké to může být? na to se zaměříme nejdříve.

Když píšeme test, představujeme si dokonalé rozhraní pro naši operaci. Malujeme si, jak bude operace vypadat zvenčí. Naše představa se často nesplní, ale je lepší vyjít z nejlepšího možného aplikačního programovacího rozhraní (API) a slevovat z něj než od začátku vidět věci složité, ošklivé a "realistické".

Zde je jednoduchý příklad na násobení:

	public void testNasobeni() {
		Dolar pet = new Dolar(5);
		pet.krat(2);
		assertEquals(10, pet.mnozstvi);
	}

Já vím, veřejné datové položky, vedlejší účinky, celočíselné hodnoty pro množství peněz a to všechno. Malé kroky. Zapíšeme si tu hrůzu do deníčku a jdeme dál. Máme nefungující test a chce se co nejrychleji dostat k pozitivním výsledkům.

$5 + 10 CHF = $10 je-li kurz 2:1

$5 * 2 = $10

Převést položku mnozstvi na soukromou

Vedlejší účinky třídy Dolar?

Zaokrouhlování peněz?

Test, kter ýjsme právě napsali, nelze dokonce ani zkompilovat (později vysvětlím, kam a jak jej napsat, až budeme mluvit o testovacím rámci JUnit). To prozatím stačí. Co musíme minimálně udělat, aby mohl být zkompilován, přestože nebude fungovat? Máme čtyři chyby v kompilaci:

Vezměme jednu chybu po druhé (vždycky hledám nějaké číselné vyjádření postupu). Jedné chyby se můžeme zbavit definováním třídy Dolar:

Dolar

class Dolar

Jedna chyba je za námi, ještě máme tři před sebou. Nyní potřebujeme konstruktor, ale nemusí nic dělat, stačí aby šel zkompilovat:

Dolar

	Dolar(int mnozstvi) {
	}

Zbývají ještě dvě chyby. Potřebujeme prázdnou implementaci metody krat(). Opět uděláme jen to nejnutnější, aby šel test přeložit:

Dolar

	void krat(int nasobek) {
	}

Zbývá poslední chyba. Nakonec potřebujeme datovou položku mnozstvi:

Dolar

	int mnozstvi;

Bingo! Nyní můžeme spustit test a sledovat, jak selže (viz obrázek 1.1).

Sledujete příšerný červený ukazatel průběhu. Náš testovací rámec (JUnit v tomto případě) spustil malý zlomek kódu, se kterým jsme začali, a oznámil, že ačkoli jsme očekávali výsledek 10, dostali jsme místo toho 0. Smůla.

Ba ne. I selhání znamená pokrok. Teď máme konkrétní měřítko neúspěchu. To je lepší než pouhé vágní konstatování, že jsme selhali. Náš problém se přesunul z bodu "zpracuj více měn" do bodu "zprovozni tento test a i všechny ostatní". Mnohem jednodušší. Mnohem menší prostor pro strach. Tento test můžeme zprovoznit.

Řešení se vám pravděpodobně líbit nebude, nyní však není cílem dostat dokonalé odpovědi, ale projít testem. Oběť na oltář pravdy a krásy přinesem později.

Zde je nejmenší změna, jakou si umím představit, která zajistí úspěšnost testu.

Dolar

	int mnozstvi = 10;
Pokrok! Test nefunguje.

Obrázek 1.2 ukzauje výsledek při opětovném spuštění testu. Teď jsme dostali onen zelený ukazatel průběhu, tolik opěvovaný v bájích a legendách.

Jaká radost! Jaké štěstí! Ne tak rychle, hackere (nebo hackerko). Kruh není uzavřen. Na světě je jen pár vstupů, které umožní, aby tak omezená, páchnoucí, naivní implementace uspěla. Než budeme pokračovat, musíme zobecňovat. Pamatujte si, že cyklus vypadá následovně:

  1. Přidejte malý test.
  2. Neúspěšně spusťte všechny testy.
  3. Proveďte malou změnu.
  4. Spusťte testy úspěšně.
  5. Refaktorizací odstraňte duplicitu.
Test funguje

Už jsme spustili úkoly 1 až 4. Nyní jsme připraveni odstranit duplicitu. Ale kde je duplicita? Obvykle nacházíme duplicitu mezi dvěma částmi kódu, ale zde je duplicita mezi v daty testu a daty v kódu. Nechápete? A co když to napíšeme takto:

Dolar

	int mnozstvi = 5 * 2;

Těch deset jsme někde museli vzít. V hlavě jsme si to vynásobili tak rychle, že jsme si toho ani nevšimli. Pětka jsou nyní na dvou místech a my musíme bezpodmínečně odstranit duplicitu, dříve než pokročíme dál. Pravidla hovoří jasně.

Pětku a dvojku nelze eliminovat v jediném kroku. Ale co kdzž přesuneme nastavení množství z inicializace objektu do metody krat()?

Dolar

	int mnozstvi;

	void krat(int nasobek) {
		mnozstvi = 5 * 2;
	}

Závislost a duplicita

Steve Freeman ukázal, že problém s testem a kódem není v duplicitě (se kterou jsem vás jestě neseznámil, ale slibuje, že to udělám hned jak dokončím tuto odbočku). Problém je ve vzájemné závislosti mezi kódem a testem - nemůžete změnit jedno, aniž byste změnili i to druhé. naším cílem je vytvořit jiný test, který nám dává smysl, aniž bychom měnili kód. Což není se současnou implementací možné.

Závislost je klíčovým problémem ve vývoji softwaru na všech úrovních. Máte-li detaily implementace SQL od různých dodavatelů roztroušeny v celém kódu a rozhodnete-li se změnit dodavatele, zjistíte, že váš kód je závislý na dodavateli databáze. Nemůžete změnit databázi, aniž byste změnili kód.

Vidíme-li v závislosti problém, pak duplicita je symptomem. Duplicita se nejčastěji vyskytuje ve formě zdvojené logiky - stejný výraz se objevuje na více místech v kódu. Objekty jsou výborným prostředkem pro odstranění zdvojené logiky.

Narozdíl od většiny problémů v životě, kde odstraněním symptomů se problé pouze přesune a objeví se někde jinde v horší podobě, eliminování duplicit v programech závislost skutečně odstraní. To je také důvod, proč vzniklo druhé pravidlo TDD. Odstraněním duplicit, předtim než přistoupíme k dalšímu testu, maximalizujeme šance zprovoznit další test s pouze jedinou změnou.


Test je stále úspěšný, ukazatel zůstává zelený. Pocit štěstí trvá.

Zdají se vám tyto kroky příliš malé? Pamatujte si, že TDD není o dělání malinkých krůčků, nýbrž o možnosti dělat malé krůčky. Budu snad psát každý den kód po takhle malých krůčcích? Ne. Ale když se nakonec věci podivně zvrtnou, budu rád že tuto možnost mám. Vyzkoušejte si malé krůčky na vámi vybraném příkladu. Pokud můžete dělat příliš malé kroky, můžete jistě dělat i správně velké kroky. Děláte-li pouze velké kroky, nikdy nevíte, jestli by nebyly vhodnější menší.

Ale dost řečí, kde jsme to skončili? Aha, odstraňovali jsme duplicitu mezi testem a výkonným kódem. Kde můžeme dostat pětku? Byla to hodnota předaná konstruktoru, takže když ji uložíme do proměnné mnozstvi,

Dolar

	Dolar(int mnozstvi) {
		this.mnozstvi = mnozstvi;
	}

pak ji můžeme použít v metodě krat():

Dolar

	void krat(int nasobek) {
		mnozstvi = mnozstvi * 2;
	}

Hodnota parametru nasobek je dva, takže můžeme nahradit konstantu parametrem:

Dolar

	void krat(int nasobek) {
		mnozstvi = mnozstvi * nasobek;
	}

Abychom ukázali jak hluboká je naše znalost syntaxe Javy, budeme chtít použít operátor *= (který, dlužno podotknout, odstraňuje duplicitu):

Dolar

	void krat(int nasobek) {
		mnozstvi *= nasobek;
	}

$5 + 10 CHF = $10 je-li kurz 2:1

$5 * 2 = $10

Převést položku mnozstvi na soukromou

Vedlejší účinky třídy Dolar?

Zaokrouhlování peněz?

Nyní můžeme označit první úkol jako splněný. Dále se zaměříme na ty zvláštní vedlejší účinky. Ale nejprve si to zopakujeme. Udělali jsme toto:

Vývoj řízený testy, Část I | Dr3dweRkZ | Vývoj řízený testy, Kapitola 2