3. diel - Testovanie v C# .NET - Dokončenie unit testov
V minulej lekcii, Testovanie v C# .NET - Úvod do unit testov , sme si pripravili jednoduchú triedu a vygenerovali testovací projekt s potrebnou referenciou.
V dnešnom tutoriále Testovanie v C# .NET pokryjeme testy našu jednoduchú triedu, uvedieme si dostupné asserčné metódy a unit testy v C# .NET dovŕšime prehľadom best practices.
Jednotlivé metódy budeme vždy označovať atribútom
[TestMethod]
. Zaistíme tým, že sa bude testovať jedna
konkrétna metóda z triedy Calculator
, typicky pre niekoľko
rôznych vstupov.
Metódy označujeme atribúty, pretože nám
to umožňuje vytvoriť si aj pomocné metódy, ktoré môžeme
v danom teste využívať, a ktoré nebudú pokladané za testy. Visual Studio
nám totiž testy (metódy s anotáciou [TestMethod]
) automaticky
spustí a vypíše ich výsledky.
Metódy triedy
CalculatorTests
Do nášho projektu, do triedy CalculatorTests
, pridáme päť
nasledujúcich metód:
[TestMethod] public void Add() { Assert.AreEqual(2, calculator.Add(1, 1)); Assert.AreEqual(1.42, calculator.Add(3.14, -1.72), 0.001); Assert.AreEqual(2.0 / 3, calculator.Add(1.0 / 3, 1.0 / 3), 0.001); } [TestMethod] public void Subtract() { Assert.AreEqual(0, calculator.Subtract(1, 1)); Assert.AreEqual(4.86, calculator.Subtract(3.14, -1.72), 0.001); Assert.AreEqual(2.0 / 3, calculator.Subtract(1.0 / 3, -1.0 / 3), 0.001); } [TestMethod] public void Multiply() { Assert.AreEqual(2, calculator.Multiply(1, 2)); Assert.AreEqual(-5.4008, calculator.Multiply(3.14, -1.72), 0.001); Assert.AreEqual(0.111, calculator.Multiply(1.0 / 3, 1.0 / 3), 0.001); } [TestMethod] public void Divide() { Assert.AreEqual(2, calculator.Divide(4, 2)); Assert.AreEqual(-1.826, calculator.Divide(3.14, -1.72), 0.001); Assert.AreEqual(1, calculator.Divide(1.0 / 3, 1.0 / 3)); } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void DivideException() { calculator.Divide(2, 0); }
Na porovnávanie výstupu metódy s očakávanou hodnotou používame
statické metódy na triede Assert
. Najčastejšie
používame metódu AreEqual()
, ktorá prijíma ako prvý parameter
očakávanú hodnotu a ako druhý parameter hodnotu
aktuálnu.
Poradie parametrov je dobré dodržiavať, inak budeme mať hodnoty vo výsledkoch testov opačne.
Desatinné čísla sú v pamäti počítača reprezentované binárne (ako inak ). To spôsobí určitú stratu ich presnosti, a tiež určité ťažkosti pri ich porovnávaní. Preto musíme v tomto prípade zadať aj tretí parameter. Týmto parametrom je delta, teda kladná tolerancia, o koľko sa môže očakávaná a aktuálna hodnota líšiť, aby test stále prešiel.
Skúšame rôzne vstupy. Sčítanie netestujeme iba ako
1 + 1 = 2
. Skúšame celočíselné,
desatinné aj negatívne vstupy, a to oddelene
s overením výsledkov. V niektorých prípadoch by nás mohla zaujímať aj
maximálna hodnota dátových typov a podobne.
Posledný test overuje, či metóda Divide()
naozaj vyvolá
výnimku pri nulovom deliteľovi. Nemusíme sa zaťažovať s
try-catch
blokmi, stačí nad metódu pridať atribút
[ExpectedException]
a uviesť tu typ výnimky,
ktorá sa očakáva. Ak výnimka nenastane, test zlyhá. Na testovanie
viacerých prípadov vyvolania výnimky môžeme použiť ďalšie assert
metódy, viď nižšie.
Dostupné Assert
metódy
Bolo by vhodné spomenúť, že sa porovnáva s ohľadom na dátové typy.
Teda 10L
(long
) je iná hodnota, než 10
(int
). Okrem metódy AreEqual()
môžeme použiť
ešte mnoho ďalších. Snažíme sa použiť tú najviac vyhovujúcu metódu,
pretože to sprehľadňuje hlášky pri zlyhaní testov a
samozrejme aj následnú opravu.
Uveďme si niektoré dostupné Assert
metódy:
AreNotEqual()
- Používame ak chceme overiť, že sa 2 objekty NEzhodujú. Ďalšie metódy sNot
tu už nebudeme zbytočne spomínať.AreSame()
- Skontroluje, či 2 referencie ukazujú na rovnaký objekt (porovnáva pomocou operátora==
).Equals()
- Používame v prípade, keď chceme overiť 2 objekty pomocou metódyEquals()
a zistiť, či sú rovnaké. Nepoužívame na overenie hodnoty miestoAreEqual()
.Fail()
- Spôsobí zlyhanie testov. Obvykle ju vkladáme za nejakú podmienku a dopĺňame o voliteľné parametre chybové hlásenie a parametre.Inconclusive()
- Funguje podobne akoFail()
. Vyvolá výnimku signalizujúcu nepreukaznosť testu.IsFalse()
- Overí, či je daný výraz NEpravdivý.IsInstanceOfType()
- Overí, či je objekt inštancií daného typu.IsNull()
- Overí, či je hodnotanull
.IsTrue()
- Overí, či je daný výraz pravdivý.ReplaceNullChars()
- Nahradí nulové znaky\0
za\\0
. Využijeme najmä pri diagnostických výpisoch reťazcov s týmito znakmi.ThrowsException()
- Spustí odovzdaný delegát a overí, že vyvoláva výnimku odovzdanú ako generický argument. Metóda má aj asynchrónnu verziuThrowsExceptionAsync()
.
Nenechajme sa zmiasť metódou ReferenceEquals()
, ktorá nie je
súčasťou testov, ale je štandardne na všetkých triedach.
Spustenie testov
Testy spustíme z menu Test -> Run All Tests:
Uvidíme výsledky, ktoré vyzerajú takto:
Skúsme si teraz urobiť v kalkulačke chybu, napr. zakomentujme vyvolávanie
výnimky pri delení nulou a vráťme vždy hodnotu 1
:
public double Divide(double a, double b) { // if (b == 0) // throw new ArgumentException("Cannot divide by zero!"); return 1; }
A spustite znovu naše testy:
Vidíme, že chyba je zachytená a sme na ňu upozornení. Neprešiel ako test delenia, tak test vyvolania výnimky. Môžeme kód vrátiť späť do pôvodného stavu.
Best practices
Už v lekcii Úvod do unit testov sme načali best practices. Keďže to je k unit testom v C# .NET všetko, poďme si na záver vymenovať akých častých chýb sa vyvarovať, aby sme dosiahli kvalitný výsledok:
- Testujeme špecifikáciu, nie kód. Testy nikdy nepíšeme podľa kódu nejakej metódy, ale zamýšľame sa nad tým, na čo metóda reálne slúži a čo všetko ju môže prísť ako vstup.
- Testujeme všeobecné knižnice, nie konkrétnu logiku aplikácie. Ak je logika dôležitá a všeobecná, mala by byť vyčlenená do samostatnej knižnice, a tá by mala byť potom testovaná.
- Každý test by mal byť úplne nezávislý od ostatných testov. Scenár by mal prebehnúť aj keď metódy ľubovoľne prehádžeme a žiadna metóda by po sebe nemala zanechávať nejaké zmeny (v súboroch, v databáze a podobne), ktoré by ovplyvnili ďalšie metódy. Na dosiahnutie tohto správania často pripravujeme prostredie pre jednotlivé metódy v inicializačnej metóde a prípadne po nich ešte prevedieme upratovanie v metóde upratovacej. To isté platí aj pre celé testy.
- Každý test by mal dopadnúť vždy rovnako, bez ohľadu na to, kedy ho spustíme. Pozor na testovanie generátorov náhodných výstupov a na prácu s dátumom a časom.
- Nevykonávajme duplicitné porovnanie, pokiaľ nejaký vstup už overuje iný test, nevykonávajme toto overenie znova.
- Každý scenár testuje len jednu jednotku (triedu). Náš softvér by mal byť navrhnutý tak, aby bol rozdelený na menšie triedy, ktoré majú minimálne závislosti na ostatných, a preto sa dajú jednoducho a nezávisle testovať (vzory high cohesion a low coupling).
- Ak testy vyžadujú externé služby, mali by sme ich tzv. mockovať. Tým vytvárame „falošné“ služby s rovnakým rozhraním, ktoré obvykle len podstrkujú testovacie dáta. Využitím skutočných služieb by sme porušili nezávislosť testov, pretože by sa navzájom začali ovplyvňovať. Menej elegantné riešenie je, vždy na začiatku nastaviť a na konci vrátiť stav služieb.
- Ako platí aj všade inde, vyhnime sa zavádzajúcim
názvom testov (ako
Calculation()
,Exception()
a podobne). Programátori často pomenovávajú testy aj väčším počtom slov, aby sa poznalo čo robia. Bežne by sme to pri metódach nemali robiť, pretože každá metóda robí len jednu činnosť, ale pri testoch dáva niekedy zmysel pomenovať metódy napr. aj takto obskurneQuadraticEquation_NegativeCoefficients_Exception()
, pretože test často testuje viac vstupov. Ideálne by mal názov testu obsahovať názov metódy, ktorú testuje. V pomenovávaní testov by sme mali byť konzistentné. Nebojme sa ani komentárov. - Testy by mali prebehnúť rýchlo, pretože v praxi obvykle testujeme všetky časti aplikácie rôznymi typmi testov a všetky časy sa dokážu nasčítať do nepríjemnej pauzy.
Naše prvé unit testy nemusia byť perfektné, stačí krátko otestovať to najdôležitejšie. Časom sa začnú odhaľovať chyby v implementácii. Čím je aplikácia väčšia, tým o väčšie pokrytie testami (test code coverage) by sme sa mali snažiť.
V budúcej lekcii, Testovanie v C# .NET - Akceptačné testy - Príprava projektu, si uvedieme akceptačné testovanie pomocou Selenium. Začneme pracovať na webovej aplikácii v ASP.NET Core MVC.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 1x (74.91 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#