Přidej si svou IT školu do profilu a najdi spolužáky zde na síti :)

3. díl - Testování v C# .NET - Dokončení unit testů a best practices

C# .NET Testování Testování v C# .NET - Dokončení unit testů a best practices

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Testování v C# .NET - Úvod do unit testů a příprava projektu, jsme si připravili jednoduchou třídu a vygenerovali testovací projekt s potřebnou referencí. Dnes pokryjeme testy naši jednoduchou třídu, uvedeme si dostupné asserční metody a unit testy v C# .NET dovršíme přehledem best practices.

Jednotlivé metody budou vždy označené atributem [TestMethod] a budou testovat jednu konkrétní metodu z třídy Kalkulacka, typicky pro několik různých vstupů. Pokud vás napadá proč metody označujeme atributy, umožňuje nám to vytvořit si i pomocné metody, které můžeme v daném testu využívat a které nebudou pokládány za testy. Visual Studio nám totiž testy (metody s anotací [TestMethod]) automaticky spustí a vypíše jejich výsledky.

Přidejme následujících 5 metod:

[TestMethod]
public void Secti()
{
        Assert.AreEqual(2, kalkulacka.Secti(1, 1));
        Assert.AreEqual(1.42, kalkulacka.Secti(3.14, -1.72), 0.001);
        Assert.AreEqual(2.0 / 3, kalkulacka.Secti(1.0 / 3, 1.0 / 3), 0.001);
}

[TestMethod]
public void Odecti()
{
        Assert.AreEqual(0, kalkulacka.Odecti(1, 1));
        Assert.AreEqual(4.86, kalkulacka.Odecti(3.14, -1.72), 0.001);
        Assert.AreEqual(2.0 / 3, kalkulacka.Odecti(1.0 / 3, -1.0 / 3), 0.001);
}

[TestMethod]
public void Vynasob()
{
        Assert.AreEqual(2, kalkulacka.Vynasob(1, 2));
        Assert.AreEqual(-5.4008, kalkulacka.Vynasob(3.14, -1.72), 0.001);
        Assert.AreEqual(0.111, kalkulacka.Vynasob(1.0 / 3, 1.0 / 3), 0.001);
}

[TestMethod]
public void Vydel()
{
        Assert.AreEqual(2, kalkulacka.Vydel(4, 2));
        Assert.AreEqual(-1.826, kalkulacka.Vydel(3.14, -1.72), 0.001);
        Assert.AreEqual(1, kalkulacka.Vydel(1.0 / 3, 1.0 / 3));
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void VydelVyjimka()
{
        kalkulacka.Vydel(2, 0);
}

K porovnávání výstupu metody s očekávanou hodnotou používáme statické metody na třídě Assert. Nejčastěji asi použijete metodu AreEqual(), která přijímá jako první parametr očekávanou hodnotu a jako druhý parametr hodnotu aktuální. Toto pořadí je dobré dodržovat, jinak budete mít hodnoty ve výsledcích testů opačně. Jak asi víte, desetinná čísla jsou v paměti počítače reprezentována binárně (jak jinak :) ) a to způsobí určitou ztrátu jejich přesnosti a také určité obtíže při jejich porovnávání. Proto musíme v tomto případě zadat i třetí parametr a to je delta, tedy kladná tolerance, o kolik se může očekávaná a aktuální hodnota lišit, aby test stále prošel.

Všimněte si, že zkoušíme různé vstupy. Sčítání netestujeme jen jako 1 + 1 = 2, ale zkusíme celočíselné, desetinné i negativní vstupy, odděleně, a ověříme výsledky. V některých případech by nás mohla zajímat také maximální hodnota datových typů a podobně.

Poslední test ověřuje, zda metoda Vydel() opravdu vyvolá výjimku při nulovém děliteli. Jak vidíte, nemusíme se zatěžovat s try-catch bloky, stačí nad metodu přidat atribut [ExpectedException] a uvést zde typ výjimky, která se očekává. Pokud výjimka nenastane, test selže. Pro testování více případů vyvolání výjimky můžeme použít další assert metody, viz níže.

Dostupné assert metody

Bylo by vhodné zmínit, že se porovnává s ohledem na datové typy, tedy 10L (long) je jiná hodnota než 10 (int). Kromě metody AreEqual() můžeme použít ještě mnoho dalších, určitě se snažte použít tu nejvíce vyhovující metodu, zpřehledňuje to hlášky při selhání testů a samozřejmě i následnou opravu.

  • AreNotEqual() - Používáme pokud chceme ověřit, že se 2 objekty NEshodují. Další metody s Not zde již nebudeme zbytečně zmiňovat.
  • AreSame() - Zkontroluje, zda 2 reference ukazují na stejný objekt (porovnává pomocí ==).
  • Equals() - Používáme v přídě, když chceme ověřit 2 objekty pomocí metody Equals() a zjistit, zda jsou stejné. Nepoužíváme pro ověření hodnoty místo AreEqual().
  • Fail() - Způsobí selhání testů, obvykle vkládáme za nějakou podmínku a doplňujeme o volitené parametry, kterými jsou chybová hláška a parametry.
  • Inconclusive() - Funguje podobně jako Fail(), vyvolá výjimku signalizující neprůkaznost testu.
  • IsFalse() - Ověří, zda je daný výraz NEpravdivý.
  • IsInstanceOfTy­pe() - Ověří, zda je objekt instancí daného typu.
  • IsNull() - Ověří, zda je hodnota null.
  • IsTrue() - Ověří, zda je daný výraz pravdivý.
  • ReplaceNullChar­s() - Nahradí nullové znaky ("\0") za "\\0", využijeme zejména u diagnostických výpisů řetězců s těmito znaky.
  • ThrowsException() - Spustí předaný delegát a ověří, že vyvolává výjimku předanou jako generický argument. Metoda má také asynchronní verzi ThrowsExceptionAsync().

Nenechte se zmást metodou ReferenceEquals(), která není součástí testů, ale je standardně na všech třídách.

Spuštění testů

Testy spustíme z menu Test -> Run -> All Tests:

Spuštění unit testů ve Visual Studio

Uvidíme výsledky, které vypadají nějak takto:

Výsledky unit testů ve Visual Studio

Zkusme si nyní udělat v kalkulačce chybu, např. zakomentujme vyvolávání výjimky při dělení nulou a vraťme vždy hodnotu 1:

public double Vydel(double a, double b)
{
        // if (b == 0)
        //      throw new ArgumentException("Nulou nelze dělit!");
        return 1;
}

A spusťme znovu naše testy:

Chyba v C# .NET unit testech

Vidíme, že chyba je zachycena a jsme na ni upozorněni. Neprošel jak test dělení, tak test vyvolání výjimky. Můžeme kód vrátit zpět do původního stavu.

Best practices

Již v minulé lekci jsme nakousli best practices. Jelikož to je k unit testům v C# .NET vše, pojďme si na závěr vyjmenovat jakých častých chyb se vyvarovat, abychom dosáhli kvalitního výsledku.

  • Testujeme specifikaci, nikoli kód. Testy nikdy nepíšeme podle kódu nějaké metody, ale zamýšlíme se nad tím, k čemu metoda reálně slouží a co vše ji může přijít jako vstup.
  • Testujeme obecné knihovny, ne konkrétní logiku aplikace. Pokud je logika důležitá a obecná, měla by být vyčleněná do samostatné knihovny a ta by měla být poté testována.
  • Každý test by měl být úplně nezávislý na ostatních testech. Scénář by měl proběhnout i když metody libovolně proházíme a žádná metoda by po sobě neměla zanechávat nějaké změny (v souborech, v databázi a podobně), které by ovlivnily další metody. K dosažení tohoto chování často připravujeme prostředí pro jednotlivé metody v inicializační metodě a případně po nich ještě provedeme úklid v metodě úklidové. To samé platí i pro celé testy.
  • Každý test by měl dopadnout vždy stejně, bez ohledu na to, kdy jej spustíme. Pozor na testování generátorů náhodných výstupů a na práci s datem a časem.
  • Neprovádějte duplicitní porovnání, pokud nějaký vstup již ověřuje jiný test, neprovádějte toto ověření znovu.
  • Každý scénář testuje jen jednu jednotku (třídu). Váš software by měl být navržený tak, aby byl rozdělený na menší třídy, které mají minimální závislosti na ostatních a proto se dají jednoduše a nezávisle testovat (vzory high cohesion a low coupling).
  • Pokud testy vyžadují externí služby, měli bychom je tzv. mockovat. Tím vytváříme "falešné" služby se stejným rozhraním, které obvykle jen podstrkují testovací data. Využitím skutečných služeb bychom porušili nezávislost testů, jelikož by se navzájem začaly ovlivňovat. Méně elegantní řešení je vždy na začátku nastavit a na konci vrátit stav služeb.
  • Jako platí i všude jinde, vyhněte se zavádějícím názvům testů (jako vypocet(), vyjimka() a podobně). Programátoři často pojmenovávají testy i větším počtem slov, aby se poznalo co dělají. Běžně bychom to u metod neměli dělat, jelikož každá metoda dělá jen jednu činnost, ale u testů dává někdy smysl pojmenovat metody např. i takto obskurně KvadratickaRovnice_ZaporneKoeficienty_Vyjimka(), protože test často testuje více vstupů. Ideálně by měl název testu obsahovat název metody, kterou testuje. V pojmenovávání testů byste měli být konzistentní. Nebojte se ani komentářů.
  • Testy by měly proběhnout rychle, jelikož v praxi obvykle testujeme všechny části aplikace různými typy testů a všechny časy se dokáží nasčítat do nepříjemné pauzy.

Vaše první unit testy nemusí být perfektní, stačí krátce otestovat to nejdůležitější. Uvidíte, že vám časem začnou selhávat a odhalovat chyby v implementaci. Čím je aplikace větší, tím o větší pokrytí testy (test code coverage) bychom se měli snažit. V příští lekci, , se podíváme na testy akceptační.


 

Stáhnout

Staženo 15x (3.91 MB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
7 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity (7)

 

 

Komentáře

Avatar
Vetva
Člen
Avatar
Vetva:26. dubna 15:16

Convertoval som tento kód do VB.net a svieti mi tam Warning :
There was a mismatch between the processor architecture of the project being built "MSIL" and the processor architecture of the reference "D......", "x86". This mismatch may cause runtime failures. Please consider changing the targeted processor architecture of your project through the Configuration Manager so as to align the processor architectures between your project and references, or take a dependency on references with a processor architecture that matches the targeted processor architecture of your project. KalkulackaAppTests
Okrem testu VydelVýjimka sú ostatné červené !

 
Odpovědět 26. dubna 15:16
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Vetva
David Čápka:26. dubna 16:09

Mně vždy dostane, jak sem opíšete tu anglickou větu, která vám přesně říká co máte dělat :) Vždyť to není těžké přeložit. Je tam napsané, že projekty jsou nastavené každý na jinou architekturu, asi jsi to špatně založil během konvertování. Oprav si to v nastavení projektu, aby projekty a reference měly tu samou architekturu (třeba x86).

Odpovědět  +1 26. dubna 16:09
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
dave_23
Redaktor
Avatar
dave_23:27. dubna 21:16

Jenom drobnost ve druhym odstavci...

Visual Studio nám totiž testy (metody s anotací [TestCleanup]) automaticky spustí a vypíše jejich výsledky.

...neměl by bejt v závorce spíš atribut [TestMethod] ?

 
Odpovědět 27. dubna 21:16
Avatar
Odpovídá na David Čápka
Michal Žůrek (misaz):27. dubna 21:23

řekl bych, že ji neopisoval, ale zkopíroval...

Odpovědět 27. dubna 21:23
Nesnáším {}, proto se jim vyhýbám.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na dave_23
David Čápka:28. dubna 9:40

Jasně, díky, opraveno :)

Odpovědět  +1 28. dubna 9:40
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 5 zpráv z 5.