Lekce 2 - Testování v C# .NET - Úvod do unit testů a příprava projektu
V minulé lekci, Testování v C# .NET - Úvod do testování , jsme si udělali poměrně solidní úvod do problematiky testování v C# .NET. Také jsme si uvedli v-model, který znázorňuje vztah mezi jednotlivými výstupy fází návrhu a příslušnými testy.
V dnešním tutoriálu Testování v C# .NET se budeme věnovat jednotkovým testům (unit testy), které testují detailní specifikaci aplikace, tedy její třídy.
Unit testy tedy píšeme vždy na základě návrhu, nikoli implementace. Jinými slovy, děláme je na základě očekávané funkčnosti. Ta může být buď přímo od zákazníka (a to v případě akceptačních testů) nebo již od programátora (architekta). Ve funkčnosti je specifikováno, jak se má která metoda chovat.
Pamatujme si, že nikdy nepíšeme testy podle toho, jak je něco uvnitř naprogramované! Velmi jednoduše by to mohlo naše myšlení svést jen tím daným způsobem a zapomněli bychom na to, že metodě mohou přijít třeba i jiné vstupy, na které není vůbec připravená. Testování s implementací ve skutečnosti vůbec nesouvisí.
Vždy testujeme zda je splněno zadání.
Jaké třídy testujeme
Unit testy testují jednotlivé metody ve třídách. Nemá valný smysl testovat jednoúčelové metody např. v modelech, které např. pouze něco vybírají z databáze. Abychom byli konkrétnější, nemá smysl testovat metodu jako je tato:
public void VlozPolozku(string nazev, double cena) { using (var db = new DatabaseEntities()) { db.polozky.Add(new Polozka(nazev, cena)); db.SaveChanges(); } }
Metoda přidává položku do databáze. Typicky je použita jen v nějakém formuláři. Pokud by metoda nefungovala, zjistí to akceptační testy, jelikož by se nová položka neobjevila v seznamu. Podobných metod je v aplikaci hodně a zbytečně bychom ztráceli čas pokrýváním něčeho, co snadno pokryjeme v jiných testech.
Unit testy nalezneme nejčastěji u knihoven, tedy nástrojů, které programátor používá na více místech nebo dokonce ve více projektech a měly by být 100% funkční. Když použijeme nějakou knihovnu, např. staženou z GitHubu, velmi pravděpodobně budou u ní také testy.
Pokud např. píšeme aplikaci, ve které často potřebujeme nějaké matematické výpočty (např. faktoriály a další pravděpodobnostní funkce), je samozřejmostí vytvořit si na tyto výpočty knihovnu a je velmi dobrý nápad pokrýt takovou knihovnu testy.
My si podobnou třídu vytvoříme a zkusíme si ji otestovat. Abychom se nezdržovali, budeme tvořit pouze jednoduchou kalkulačku, která bude umět:
- sčítat,
- odčítat,
- násobit,
- dělit.
Vytvoření projektu
V praxi by ve třídě byly nějaké složitější výpočty, ale tím se
zde zabývat nebudeme. Vytvořme si nový projekt,
konzolovou aplikaci, s názvem KalkulackaApp
. Do
něj si přidáme veřejnou (public
) třídu
Kalkulacka
s následující implementací:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace KalkulackaApp { /// <summary> /// Reprezentuje jednoduchou kalkulačku /// </summary> public class Kalkulacka { /// <summary> /// Sečte 2 čísla /// </summary> /// <param name="a">První číslo</param> /// <param name="b">Druhé číslo</param> /// <returns>Součet 2 čísel</returns> public double Secti(double a, double b) { return a + b; } /// <summary> /// Odečte 2 čísla /// </summary> /// <param name="a">První číslo</param> /// <param name="b">Druhé číslo</param> /// <returns>Rozdíl 2 čísel</returns> public double Odecti(double a, double b) { return a - b; } /// <summary> /// Vynásobí 2 čísla /// </summary> /// <param name="a">První číslo</param> /// <param name="b">Druhé číslo</param> /// <returns>Součin 2 čísel</returns> public double Vynasob(double a, double b) { return a * b; } /// <summary> /// Vydělí 2 čísla /// </summary> /// <param name="a">První číslo</param> /// <param name="b">Druhé číslo</param> /// <returns>Podíl 2 čísel</returns> public double Vydel(double a, double b) { if (b == 0) throw new ArgumentException("Nulou nelze dělit!"); return a / b; } } }
Na kódu je zajímavá pouze metoda Vydel()
, která vyvolá
výjimku v případě, že dělíme nulou.
Výchozí chování C# .NET pro desetinná čísla je vrácení
hodnoty Infinity
, což není vždy to, co uživatel aplikace
očekává.
UnitTesting
V C# .NET se unit testy píší pomocí nástrojů ve jmenném prostoru
Microsoft.VisualStudio.TestTools.UnitTesting
. Visual Studio
poskytuje plnou podporu těchto testů a ke své aplikaci je přidáme jako
další projekt do solution. Testy tedy budou od projektu úplně
oddělené, což je návrhově velká výhoda. Pouze nesmíme zapomenou
projekty propojit příslušnými referencemi.
V Solution Explorer klikneme na Solution KalkulackaApp pravým tlačítkem a zvolíme Add -> New Project...

Vybereme šablonu MSTest Test Project:

Název projektu s testy se zpravidla sestavuje jako název projektu aplikace
+ slovo "Tests", v našem případě tedy KalkulackaAppTests
:

V dalším okně potvrdíme typ frameworku NET 6.0:

Do testovacího projektu nyní musíme přidat referenci na projekt s aplikací, abychom mohli přistupovat k příslušným třídám. To provedeme kliknutím pravým tlačítkem na projekt KalkulackaAppTests a zvolením Add -> Project Reference...

V následujícím formuláři vybereme záložku Projects ->
Solution a zaškrtneme projekt KalkulackaApp
. Dialog
potvrdíme a tím si zpřístupníme třídu Kalkulacka
:

V projektu KalkulackaAppTest
se nám vygeneroval nový soubor
UnitTest1.cs
s následujícím kódem:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace KalkulackaAppTests { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { } } }
Asi nás v objektovém C# nepřekvapí, že je test třídy (scénář)
reprezentovaný také třídou a jednotlivé testy metodami S atributy
(hranatými závorkami nad metodami a třídami) jsme se již v seriálu setkali
a víme, že slouží ke specifikování nějakých informací.
Atribut [TestClass]
zde označuje testovací
scénář. Pomocí atributu [TestMethod]
označujeme
metody, které reprezentují jednotlivé testy (budou
automaticky spouštěné Visual Studiem). Třídu UnitTest1
(i
její soubor) si přejmenujeme na KalkulackaTests
, jelikož bude
obsahovat testy pro třídu Kalkulacka
.
Pokrytí třídy testy
V unit testech můžeme použít ještě několik dalších atributů. My
nyní využijeme atribut [TestInitialize]
a pro názornost i
atribut [TestCleanup]
. Těmito atributy označujeme metody, které
se zavolají před, resp. po každém testu v této třídě.
To je pro nás velmi důležité, jelikož podle best practices chceme, aby byly
testy nezávislé.
Obvykle tedy před každým testem připravujeme znovu to samé prostředí,
aby se vzájemně vůbec neovlivňovaly. O dobrých praktikách se zmíníme
detailněji později. Do třídy si nejprve přidejme jmenný prostor
using KalkulackaApp
. Teď si již můžeme, ve třídě
KalkulackaTests
, vytvořit private
proměnnou
kalkulacka
.
V metodě s anotací [TestInitialize]
si do proměnné
kalkulacka
vytvoříme čerstvě novou kalkulačku pro každý
test. Pokud by ji bylo ještě třeba dále nastavovat, nebo bylo třeba
vytvořit další závislosti, byly by také v této metodě. Metodu
TestMethod1()
odstraníme.
Kód třídy KalkulackaTests
je následující:
using System; using KalkulackaApp; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace KalkulackaAppTests { [TestClass] public class KalkulackaTests { private Kalkulacka kalkulacka; [TestInitialize] public void Initialize() { kalkulacka = new Kalkulacka(); // Vytvoří novou kalkulačku před každým testem } [TestCleanup] public void Cleanup() { } }
Máme vše připraveno k přidávání samotných testů.
V příští lekci, Testování v C# .NET - Dokončení unit testů a best practices, pokryjeme třídu unit testy, vysvětlíme si
metody na třídě Assert
a naučíme se testovat výjimky.
Zmíníme best practices.