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 sNot
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í metodyEquals()
a zjistit, zda jsou stejné. Nepoužíváme pro ověření hodnoty místoAreEqual()
.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ě jakoFail()
. 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 hodnotanull
.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í verziThrowsExceptionAsync()
.
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:

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

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:

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 157x (1.89 MB)
Aplikace je včetně zdrojových kódů v jazyce C#