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čnost | Akcie | Cena | Celkem |
IBM | 1000 | 25 | 25000 |
GE | 400 | 100 | 40000 |
Celkem | 65000 |
Máme-li vytvořit sestavu pro více měn, musíme přidat měny:
Společnost | Akcie | Cena | Celkem |
IBM | 1000 | 25 USD | 25000 USD |
Novartis | 400 | 150 CHF | 60000 CHF |
Celkem | 65000 USD |
Také musíme určit kurz:
Z | Na | Kurz |
CHF | USD | 1,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ě?
- Musíme být schopni zadat množství ve dvou různých měnách a převést výsledek podle daného kurzu.
- Musíme být schopni vynásobit množství (cena za akcii) počtem (počet akcií) a získat množství.
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:
- Chybí třída Dolar
- Chybí konstruktor
- Chybí metoda krat(int)
- Chybí atribut mnozstvi
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;
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ě:
- Přidejte malý test.
- Neúspěšně spusťte všechny testy.
- Proveďte malou změnu.
- Spusťte testy úspěšně.
- Refaktorizací odstraňte duplicitu.
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:
- Vytvořili jsme seznam testů, o kteerých víme, že musí fungovat.
- Vyprávěli jsme si příběh s kouskem kódu o tom, jak chceme vidět jednu operaci.
- na chvíli jsme ignorovali detaily o JUnit
- Přeložili jsme test s prázdnými metodami.
- Za cenu spáchání hříchů jsme zprovoznili test.
- Postupným zobecňováním výkonného kódu jsme nahradili konstanty proměnnými.
- Místo toho, abychom se zabývali všemi úkoly najednou, přidali jsme je na náš seznam.
Vývoj řízený testy, Část I | Dr3dweRkZ | Vývoj řízený testy, Kapitola 2