Úvod
Snad každý vývojář či tým, který někdy pracoval na nějakém větším produktu, mi asi dá zapravdu, že se zautomatizovaným testováním se vyvíjí lépe a rychleji (ve většině případů). Toho si jsou samozřejmě vědomi i výrobci developerských nástrojů a tak vyvíjí různé více či méně povedené testovací nástroje. Microsoft samozřejmě nemohl zůstat stranou a tak máme testy i ve Visual Studiu. VS podporuje všechny testy v Tester a Team System edicích, nicméně Unit testy, o kterých bude většina tohoto článku, jsou obsaženy už v VS 2008 Profesional. Pokud žádnou z těchto edicí nevlastníte, můžete využít například pěkný opensourcový framework NUnit.
Na začátku by bylo asi dobré uvést, proč a kdy se nám testy vyplatí a kdy nevyplatí. Tvrzení, že se vyplatí, když nám to ušetří peníze/čas/nervy, je sice matematicky přesné ale o ničem to nevypovídá. Pravda je taková, že záleží na hodně faktorech – na typu projektu (např. algoritmy se automaticky testují lépe než klikání po GUI), zkušenostech vývojáře a tak. Obecně se dá říct, že testování pomocí dobře napsaných testů přispívá k menší chybovosti (dobrý test prozkouší velké množství případů a hned přehledně vidíte (v ideálním případě) obrazovku plnou zelených fajfčiček, případně, co kde jste co zkazili – tohle dělat ručně je hrozná otrava), šetří čas (zmáčknout pár tlačítek je určitě rychlejší než ruční “proklikání” aplikace, na druhou stranu napsat dobré testy stojí čas, takže je třeba to vyvážit), vývojář se na svůj kód podívá i z jiné perspektivy atd. Některé lepší verzovací systémy (např. Team Foundation Server) pak podporují automatické spouštění testů každý den nebo po commitu (takže se do repository nedostane kód, který by neprošel testy). To by se s ručním testováním dělalo špatně. Seznam není úplný a rozhodně záleží na projektu. A co takový dobrý test by měl splňovat? Asi nejdůležitějším kritériem je pokrytí co nejvíce případů a co největší části kódu (tzv. code coverage). Kromě běžných hodnot a testovacích podmínek je také dobré testovat i krajní hodnoty a podmínky. Je totiž asi 99.9% pravděpobnost, že nastanou, obzvlášť pokud jste je neotestovali :)
Dají se vést dlouhé filosofické debaty na téma, jak psát dobré testy, kdo by je měl psát, kdy by se měli psát atd. Do těch se pouštět nebudu a radši popíšu, jak začít s testováním ve VS.
Vytváříme unit test
Testovací třída
Nejběžnější a základní test je tzv unit test. V principu jde o kus kódu, který (typicky) používá třídu či třídy z testované aplikace, a nějakým způsobem zkouší jejich korektnost a rychlost. V jednoduchém případě např. porovnává výsledky testovaného kódu s nějakými předpočítanými. Z formálního hlediska je testovací třída velmi jednoduchá – je to public třída s atributem TestClassAttribute umístěná v testovacím projektu (což je normální projekt, jen v něm pro přehlednost není něco-dělající kód, ale pouze testy) s public metodami s atributy TestMethodAttribute. Další důležité atributy pro metody v testovací třídě jsou ClassInitializeAttribute resp. ClassCleanupAttribute, které se spouští před prvním testem třídy resp. po posledním testu a pak atributy TestInitializeAttribute a TestCleanupAttribute, které se spouští před a po každém testu. Kostru testovací třídy můžete snadno nakliknout v menu Test, položka New Test.
Asserty a ExpectedException
Druhá důležitá věc, kterou je třeba při psaní test metod znát je statická třída Assert (namespace Microsoft.VisualStudio.TestTools.UnitTesting) obsahující metody jako je AreSame, IsTrue, AreEqual a jejich znegované protějšky, pomocí nichž kontolujete, jestli platí invarianty, výsledky jsou správně, prostě jestli je vše OK. Kromě metod jednoznačně určujících failnutí testu je tam i metoda Inconclusive – test, kde se zavolala, má pak ve “výsledkové listině” u sebe oranžový otazníček na znamení nevyhodnotitelnosti testu. Assertů samozřejmě můžete mít v test metodě víc a platí, že test uspěl právě tehdy, když žádný assert nefailnul a nebyla tam nezachycená výjimka (pokud nepoužijeme ExpectedExceptionAttribute) a nebyla zavolána ani Inconclusive.
Kromě testování podmínek pomocí assertů můžete taky testovat, jestli se vyhodí nějaká výjímka nebo ne. Ve výchozím stavu nezachycená výjímka automaticky způsobí failnutý test. Nicméně člověk typicky potřebuje otestovat i chybové stavy a z toho důvodu nám vývojáři testovacího frameworku nadělili atribut ExpectedExceptionAttribute, který se dá vrazit testovací metodě a způsobí, že test failne, pokud daná výjimka (typ výjimky se dává konstruktoru toho atributu) nebyla vyhozena nebo byla zachycena.
Dost bylo řečí, je čas na nějaký ten příklad (příklad jsou z jednoho docela velkého úkolu, co jsme museli dělat kvůli zápočtu do C#, testy v něm byly jednoduché a pěkně ilustrativní):
[TestClass]
public class UnitTesty
{
[TestMethod]
public void XorGate()
{
int line = 0;
Gate gt = new Gate("gate xor", GetStreamReader(XOR), ref line); // vytvoření testovaného objektu
Assert.AreEqual("xor", gt.Name); // první ověření, ověřuje se správné naparsování
GateInstance g = new GateInstance("xor", gt); // vytvoření druhého testovaného objektu
g.NewInput(GateValue.False, GateValue.True); g.Tick(); // výpočet
GateValue[] val = { GateValue.True }; // předpočítaný výsledek
Assert.IsTrue(AbstractGate.CompareGateValues(val, g.Output), VisualizeGateArray(g.Output)); // porovnání předpočítaného výsledku se spočítaným. V případě, že se liší, tak test neuspěje a do logu se napíše to, co vrátí fce VisualizeGateArray
// ... metoda pokračuje podobnými testy
}
[TestMethod, ExpectedException(typeof(MissingSymbolCompileException))]
public void GateMissingSymbol()
{
int line = 0;
Gate g = new Gate("gate and", GetStreamReader(AND5), ref line); // tahle metoda s chybnými parametry by měla vyhodit MissingSymbolCompileException
}
string VisualizeGateArray(GateValue[] values) { /* ... */ }
StreamReader GetStreamReader(string s) { /* ... */ }
}
Následující odstavce se týkají všech typů testu, nejen Unit testů:
Je dobré psát testovací metody tak, aby nezávisely na sobě (např. změnou globálního stavu), neboť defaultně je pořádí spouštění testů nedefinované. Toto se dá obejít tzv. Ordered Test Listy, což je uspořádaný seznam testů, které se pak provádí v daném pořadí. Vytvoření Ordered Testu není nic těžkého, pomocí New Test jen vytvoříte Ordered Test a pak si naklikáte, které testy tam budou a v jakém pořadí se budou spouštět. Ordered Test se pak spouští ve stejném okně jako ostatní testy.
Přístup k private a protected členům
Když člověk dělá nějaký větší projekt než pár řádků (což projekty, kde se dělají automatiované testy typicky splňují), tak si dává pozor, aby public membery nebyly public zbytečně a nevystavuje ven vnitřní věci třídy. To je samozřejmě chvályhodné, ale při testech můžeme chtít testovat i vnitřní stav objektu. Co pak? Možných přístupů je několik – přenesení části test kódu do těla třídy (čímž si “výkonný” kód zaneseme testovacím, který má být jinde), pomocí Reflection atd, nicméně vždy tím uděláme nepořádek v objektovém modelu aplikace a/nebo v oddělení testovacího kódu od ostatního. Naštěstí VS umožňuje udělat “hack” třídu nazývanou Private Accessor, díky které se dostaneme i private a protected memberům. Klikátko na vytváření je docela dobře skryté – je třeba najít začátek definice testované třídy, kliknout pravomyší a tam zvolit Create Private accessor a vybrat test projekt, kam se má Accessor vložit (ten už musí být vytvořený). Druhou možností je pak položka Create Unit Tests ve stejném menu, která kromě Accessora také vygeneruje kostru pro testování vybraných metod (ve stylu “zavolá se ta metoda a danými parametry a pak se porovná výsledek s předpočítaným”). Načež se ubohá (Proč ubohá? Vám by se líbilo, kdyby někdo takhle odhalil všechny vaše soukromé (private) věci?) třída objeví v Test References daného test projektu a vy pak můžete používat třídu se jménem PůvodníNázev_Accessor, která dělá to samé jako původní třída, ale všechny membery má public. Nevýhoda tohohle postupu je, že nefunguje na 100% a všechny třídy by měly být public (asi).
Spouštění testů
Pokud máme hotový test nebo testy, můžeme ho spustit. To se nejpohodlněji dělá pomocí okna Test View, klávesové zkratky Ctrl+R,A, v kontextové nabídce kódu toho testu či nějakým jiným způsobem. Po doběhnutí všech testů pak vyjede přehledná tabulka, které testy passly a které failnuly. Můžete si také zobrazit detaily o jednotlivých testech, kde přesně to failnulo atd.
Code coverage
Jak jsem již v úvodu zmínil, jedním z indikátorů kvality testů je to, kolik kódu se testem otestuje. Učeně se tomu říká Code coverage. Nejlepší by bylo samozřejmě zkoumat i jestli kód byl použit ve všech možných variantách a se všemi možnými typy parametrů, nicméně to zatím software neumí. Ale Code coverage měřit jde.
Postup je jednoduchý. Nejdříve je třeba v Test-Edit test Configuration v záložce Code Coverage zaškrtnout všechny assembly obsahující kód, kde budeme počítat code coverage. Následně spustíme test, který chceme měřit a počkáme, než doběhne. V kontextové nabídce výsledku daného testu by měla být položka Code Coverage Results, na kterou klikneme a dostaneme se tak k výsledkům Code Coverage, pěkně ve stromové struktuře s výsledky pro jednotlivé assemblies, namespace, třídy a metody:
Šikovné je, že v kódu máte pak použité části modře podbarvené a nepoužité hnědočerveně.
Jiné typy testů
Visual Studio samozrejmě umožňuje i další typy testů, které stručně popíšu. Nejjednodušší test je tzv. Manual test, který je tvořen textem nebo Wordovským dokumentem a obsahuje instrukce pro testera, co a jak má testovat :)
Bohužel chybí nástroj pro testování GUI aplikací, takže je nutné se proklikábat ručně nebo použít nástroje třetí strany. Nemám s nimi žádnou zkušenost, UI testy jsem zatím nepotřeboval.
Web testy
Sice nemáme nástroje na proklikání UI, ale máme nástroj na proklikání webu. Hned po vytvoření testu se otevře Internet Explorer (zde naštěstí nejmenované firmě nevadí, že jejím prohlížečem to nejde) v nahrávacím režimu, kdy se zaznamenává, na které stránky lezeme a co tam vyplňujeme. Po tom, co prolezene všechna testovaná místa, ukončíme nahrávání a stopy naší cesty webem se přenesou do Visual Studia.
Ke každé navštívené stránce můžeme přiřadit tzv. Extraction Rules a Validation Rules. Extraction rules slouží k vytáhnutí nějakých informací ze stránky. Můžeme si psát své vlastní nebo použít již předdefinovaná, která umožňují tahat data z formulářů, URL, HTTP hlaviček, odpovědi apod. Extrahovaná infomace se pak uloží do kontextu testu pod názvem z políčka Context Parameter Name. Příklad ukážu za chvíli, až budu popisovat vlastní Validation rule. Validation Rules, jak název napovídá, slouží k ověření správnosti odpovědi, mají tedy podobný charakter jako Asserty u Unit testů. Zase máme několik předdefinovaných typů, já ale ukážu, jak si udělat vlastní. Celý webtest v tomto případě dělá následující: z první stránky pomocí Extract Text (viz obrázek) vytáhne text, který najde mezi značkami ****** a ////// a uloží do kontextu testu pod názvem sampleContext. U druhé stránky je pak jen ručně napsané validation rule, které se pokusí najít ve stránce to samé, jako bylo v té první, tedy “******”+text_v_prvního_webu+”//////”. Formálně je vlastní validation rule jen public třída dědící z abstraktní třídy ValidationRule a implementující metodu Validate, která se stará o vlastní validaci. Výsledek validace se pak nedává najevo Asserty, ale zapisuje do e.IsValid.
[DisplayName("SampleValidRule")] //název zobrazovaný v "Add Valid. rule" dialogu
public class SampleValidRule : ValidationRule {
public string StartString { get; set; } // properties s parametry validace
public string EndString { get; set; }
public override void Validate(object sender, ValidationEventArgs e)
{
if (e.Response.BodyString.Contains(StartString))
e.IsValid = (e.Response.BodyString.Contains(StartString + e.WebTest.Context["sampleContext"] + EndString));
else Assert.Inconclusive("nemůžu najít ty "+StartString);
}
}
S web testy samozřejmě můžete dělat i jiné a mnohem složitější věci, ale myslím si, že pro hrubou představu, jak fungují, to stačí.
Databázové testy
DB testy sestávají ze 2 hlavních částí: ze SQL dotazu, který se provede nad danou databází, a ze sady podmínek, který musí výsledek splňovat včetně např. doby běhu. Na pozadí se pak vytváří klasický Unit test.
Závěr
Co dodat? Snad jen doporučení, aby jste testy psali s rozmyslem a věnovali jim péči, kterou si zaslouží. Občas se taky říká, že je dobré, když testy píše někdo jiný – má to výhodu v tom, že každý se na problém dívá jinýma očima. A na úplný závěr bych vás rád vyzval k psaní svých postřehů jak ke článku, tak k testování samotnému do diskuze pod článkem.