IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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 s Not 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ódy Equals() a zistiť, či sú rovnaké. Nepoužívame na overenie hodnoty miesto AreEqual().
  • 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 ako Fail(). 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 hodnota null.
  • 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 verziu ThrowsExceptionAsync().

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:

testkalk_spusteni_testu - Testovanie v C# .NET - Testovanie v C# .NET

Uvidíme výsledky, ktoré vyzerajú takto:

testkalk_vysledek_testu - Testovanie v C# .NET

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:

testkalk_vysledek_s_vadou - Testovanie v C# .NET

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 obskurne QuadraticEquation_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#

 

Predchádzajúci článok
Testovanie v C# .NET - Úvod do unit testov
Všetky články v sekcii
Testovanie v C# .NET
Preskočiť článok
(neodporúčame)
Testovanie v C# .NET - Akceptačné testy - Príprava projektu
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
2 hlasov
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 sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity