3. diel - Testovanie v C# .NET - Dokončenie unit testov
V predchádzajúcej lekcii, Testovanie v C# .NET - Úvod do unit testov, sme si pripravili jednoduchú triedu a vygenerovali testovací projekt s potrebnou referenciou.
V dnešnej lekcii Testovania v C# .NET pokryjeme testami našu jednoduchú triedu, uvedieme si dostupné asserčné metódy a unit testy v C# .NET dokončíme 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 sNottu 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é. Na overenie nepoužívame hodnoty miestoAreEqual().Fail()- Spôsobí zlyhanie testov. Obvykle ju vkladáme za nejakú podmienku a dopĺňame o voliteľné parametre chybová hláška 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\0za\\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. okomentujme 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 znovu spustíme naše testy:

Vidíme, že chyba je zachytená a sme na ňu upozornení. Neprešiel ako test delenia, ani ako test vyvolania výnimky. Kód môžeme 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 by sme sa mali 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 jej 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 urobíme upratovanie v upratovacej metóde. 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, ak 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 bolo jasné, č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 zvláštneQuadraticEquation_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 sčí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 väčšie pokrytie testami (test code coverage) by sme sa mali snažiť dosiahnuť.
V nasledujú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é 7x (75.4 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

David sa informačné technológie naučil na