Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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

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í.

V dnešním tutoriálu Testování v C# .NET 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 budeme vždy označovat atributem [TestMethod]. Zajistíme tím, že se bude testovat jedna konkrétní metoda z třídy Kalkulacka, typicky pro několik různých vstupů.

Metody označujeme atributy, protože nám to umožňuje 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.

Metody třídy KalkulackaTests

Do našeho projektu, do třídy KalkulackaTests, přidáme pět následujících 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 používáme metodu AreEqual(), která přijímá jako první parametr očekávanou hodnotu a jako druhý parametr hodnotu aktuální.

Pořadí parametrů je dobré dodržovat, jinak budeme mít hodnoty ve výsledcích testů opačně.

Desetinná čísla jsou v paměti počítače reprezentována binárně (jak jinak :) ). 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. Tímto parametrem 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.

Zkoušíme různé vstupy. Sčítání netestujeme jen jako 1 + 1 = 2. Zkoušíme celočíselné, desetinné i negativní vstupy, a to odděleně s ověřením výsledků. 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. 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. Snažíme se použít tu nejvíce vyhovující metodu, protože to zpřehledňuje hlášky při selhání testů a samozřejmě i následnou opravu.

Uveďme si některé dostupné Assert metody:

  • 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í operátoru ==).
  • Equals() - Používáme v případě, 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 ji vkládáme za nějakou podmínku a doplňujeme o volitelné parametry 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ý.
  • IsInstanceOfType() - Ověří, zda je objekt instancí daného typu.
  • IsNull() - Ověří, zda je hodnota null.
  • IsTrue() - Ověří, zda je daný výraz pravdivý.
  • ReplaceNullChars() - Nahradí nulové 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().

Nenechme 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:

testkalk_spusteni_testu - Testování v C# .NET

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

testkalk_vysledek_testu - Testování v C# .NET

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:

testkalk_vysledek_s_vadou - Testování v C# .NET

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ějme duplicitní porovnání, pokud nějaký vstup již ověřuje jiný test, neprovádějme toto ověření znovu.
  • Každý scénář testuje jen jednu jednotku (třídu). Náš 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ěme 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ů bychom měli být konzistentní. Nebojme 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.

Naše první unit testy nemusí být perfektní, stačí krátce otestovat to nejdůležitější. Časem se začnou 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, Testování v C#.NET - Akceptační testy - Příprava projektu, si uvedeme akceptační testování pomocí Selenium. Začneme pracovat na webové aplikaci v ASP.NET Core MVC.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
Testování v C# .NET - Úvod do unit testů a příprava projektu
Všechny články v sekci
Testování v C# .NET
Přeskočit článek
(nedoporučujeme)
Testování v C#.NET - Akceptační testy - Příprava projektu
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
85 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity