2. diel - Testovanie v C# .NET - Úvod do unit testov
V minulej lekcii, Testovanie v C# .NET - Úvod do testovania , sme si urobili pomerne solídny úvod do problematiky testovania v C# .NET. Tiež sme si uviedli v-model, ktorý znázorňuje vzťah medzi jednotlivými výstupmi fáz návrhu a príslušnými testami.
V dnešnom tutoriále Testovania v C# .NET sa budeme venovať jednotkovým testom (unit testy), ktoré testujú detailnú špecifikáciu aplikácie, teda jej triedy.
Unit testy teda píšeme vždy na základe návrhu, nie implementácie. Inými slovami, robíme ich na základe očakávanej funkčnosti. Tá môže byť buď priamo od zákazníka (a to v prípade akceptačných testov) alebo už od programátora (architekta). Vo funkčnosti je špecifikované, ako sa má ktorá metóda správať.
Pamätajme si, že nikdy nepíšeme testy podľa toho, ako je niečo vo vnútri naprogramované! Veľmi jednoducho by to mohlo naše myslenie zviesť len tým daným spôsobom a zabudli by sme na to, že metóde môžu prísť napríklad aj iné vstupy, na ktoré nie je vôbec pripravená. Testovanie s implementáciou v skutočnosti vôbec nesúvisí.
Vždy testujeme či je splnené zadanie.
Aké triedy testujeme
Unit testy testujú jednotlivé metódy v triedach. Nemá veľký zmysel testovať jednoúčelové metódy napr. v modeloch, ktoré napr. iba niečo vyberajú z databázy. Aby sme boli konkrétnejší, nemá zmysel testovať metódu ako je táto:
public void InsertItem(string name, double price) { using (var db = new DatabaseEntities()) { db.items.Add(new Item(name, price)); db.SaveChanges(); } }
Metóda pridáva položku do databázy. Typicky je použitá len v nejakom formulári. Ak by metóda nefungovala, zistí to akceptačné testy, pretože by sa nová položka neobjavila v zozname. Podobných metód je v aplikácii veľa a zbytočne by sme strácali čas pokrývaním niečoho, čo ľahko pokryjeme v iných testoch.
Unit testy nájdeme najčastejšie pri knižniciach, teda nástrojoch, ktoré programátor používa na viacerých miestach alebo dokonca vo viacerých projektoch a mali by byť 100% funkčné. Keď použijeme nejakú knižnicu, napr. stiahnutú z GitHubu, veľmi pravdepodobne budú u nej aj testy.
Pokiaľ napr. píšeme aplikáciu, v ktorej často potrebujeme nejaké matematické výpočty (napr. faktoriály a ďalšie pravdepodobnostné funkcie), je samozrejmosťou vytvoriť si na tieto výpočty knižnicu a je veľmi dobrý nápad pokryť takúto knižnicu testami.
My si podobnú triedu vytvoríme a skúsime si ju otestovať. Aby sme sa nezdržiavali, budeme tvoriť iba jednoduchú kalkulačku, ktorá bude vedieť:
- sčítať,
- odčítať,
- násobiť,
- deliť.
Vytvorenie projektu
V praxi by v triede boli nejaké zložitejšie výpočty, ale tým sa tu
zaoberať nebudeme. Vytvorme si nový projekt,
konzolovú aplikáciu, s názvom CalculatorApp
. Do
neho si pridáme verejnú (public
) triedu
Calculator
s nasledujúcou implementáciou:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CalculatorApp { /// <summary> /// Represents a simple calculator /// </summary> public class Calculator { /// <summary> /// Adds 2 numbers /// </summary> /// <param name="a">First number</param> /// <param name="b">Second number</param> /// <returns>Sum of 2 numbers</returns> public double Add(double a, double b) { return a + b; } /// <summary> /// Subtracts 2 numbers /// </summary> /// <param name="a">First number</param> /// <param name="b">Second number</param> /// <returns>Difference of 2 numbers</returns> public double Subtract(double a, double b) { return a - b; } /// <summary> /// Multiplies 2 numbers /// </summary> /// <param name="a">First number</param> /// <param name="b">Second number</param> /// <returns>Product of 2 numbers</returns> public double Multiply(double a, double b) { return a * b; } /// <summary> /// Divides 2 numbers /// </summary> /// <param name="a">First number</param> /// <param name="b">Second number</param> /// <returns>Quotient of 2 numbers</returns> public double Divide(double a, double b) { if (b == 0) throw new ArgumentException("Cannot divide by zero!"); return a / b; } } }
Na kóde je zaujímavá iba metóda Divide()
, ktorá vyvolá
výnimku v prípade, že delíme nulou.
Predvolené správanie C# .NET pre desatinné čísla je
vrátenie hodnoty Infinity
, čo nie je vždy to, čo používateľ
aplikácie očakáva.
UnitTesting
V C# .NET sa unit testy píšu pomocou nástrojov v mennom priestore
Microsoft.VisualStudio.TestTools.UnitTesting
. Visual Studio
poskytuje plnú podporu týchto testov ak svojej aplikácii ich pridáme ako
ďalší projekt do solution. Testy teda budú od projektu úplne
oddelené, čo je návrhovo veľká výhoda. Iba nesmieme zabudnúť
projekty prepojiť príslušnými referenciami.
V Solution Explorer klikneme na Solution CalculatorApp pravým tlačidlom a zvolíme Add -> New Project...
Vyberieme šablónu MSTest Test Project:
Názov projektu s testami sa spravidla zostavuje ako názov projektu
aplikácie + slovo "Tests", v našom prípade teda
CalculatorAppTests
:
V ďalšom okne potvrdíme typ frameworku NET 6.0:
Do testovacieho projektu teraz musíme pridať referenciu na projekt s aplikáciou, aby sme mohli pristupovať k príslušným triedam. To urobíme kliknutím pravým tlačidlom na projekt CalculatorAppTests a zvolením Add - > Project Reference...
V nasledujúcom formulári vyberieme záložku Projects ->
Solution a zaškrtneme projekt CalculatorApp
. Dialóg
potvrdíme a tým si sprístupníme triedu Calculator
:
V projekte CalculatorAppTests
sa nám vygeneroval nový súbor
UnitTest1.cs
s nasledujúcim kódom:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CalculatorAppTests { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { } } }
Asi nás v objektovom C# neprekvapí, že je test triedy (scenár) reprezentovaný aj triedou a jednotlivé testy metódami S atribútmi (hranatými zátvorkami nad metódami a triedami) sme sa už v seriáli stretli a vieme, že slúžia na špecifikovanie nejakých informácií.
Atribút [TestClass]
tu označuje testovací
scenár. Pomocou atribútu [TestMethod]
označujeme
metódy, ktoré reprezentujú jednotlivé testy (budú
automaticky spúšťané Visual Studiom). Triedu UnitTest1
(aj jej
súbor) si premenujeme na CalculatorTests
, pretože bude obsahovať
testy pre triedu Calculator
.
Pokrytie triedy testy
V unit testoch môžeme použiť ešte niekoľko ďalších atribútov. My
teraz využijeme atribút [TestInitialize]
a pre názornosť aj
atribút [TestCleanup]
. Týmito atribútmi označujeme metódy,
ktoré sa zavolajú pred, resp. po každom teste v tejto
triede. To je pre nás veľmi dôležité, pretože podľa best practices
chceme, aby boli testy nezávislé.
Obvykle teda pred každým testom pripravujeme znova to isté prostredie, aby
sa vzájomne vôbec neovplyvňovali. O dobrých praktikách sa zmienime
detailnejšie neskôr. Do triedy si najskôr pridajme menný priestor
using CalculatorApp
. Teraz si už môžeme, v triede
CalculatorTests
, vytvoriť private
premennú
calculator
.
V metóde s anotáciou [TestInitialize]
si do premennej
calculator
vytvoríme čerstvo novú kalkulačku pre každý test.
Ak by ju bolo ešte potrebné ďalej nastavovať, alebo bolo treba vytvoriť
ďalšie závislosti, boli by aj v tejto metóde. Metódu
TestMethod1()
odstránime.
Kód triedy CalculatorTests
je nasledovný:
using System; using CalculatorApp; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CalculatorAppTests { [TestClass] public class CalculatorTests { private Calculator calculator; [TestInitialize] public void Initialize() { calculator = new Calculator(); // Creates a new calculator before each test } [TestCleanup] public void Cleanup() { } } }
Máme všetko pripravené na pridávanie samotných testov.
V budúcej lekcii, Testovanie v C# .NET - Dokončenie unit testov, pokryjeme triedu unit testy, vysvetlíme si
metódy na triede Assert
a naučíme sa testovať výnimky.
Spomenieme best practices.
Mal si s čímkoľvek problém? Zdrojový kód vzorovej aplikácie je k stiahnutiu každých pár lekcií. Zatiaľ pokračuj ďalej, a potom si svoju aplikáciu porovnaj so vzorom a ľahko opráv.