Základy testování aplikací pomocí Visual Studia

Jakub Čermák       7. 8. 2009       C#, Visual Studio, Testování, Architektura       11802 zobrazení

V článku jsou popsány základní metody automatizovaného testování aplikací, které jsou vestavěny ve Visual Studiu. Článek je psán pro lidi, kteří s automatickým testováním nemají zkušenosti nebo se chtějí podívat, co VS testy nabízí.

Ú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.

image

 

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.image 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.image

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:image

Š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.

image

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.

image

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.

 

hodnocení článku

2 bodů / 2 hlasů       Hodnotit mohou jen registrované uživatelé.

 

 

 

Nový příspěvek

 

Diskuse: Základy testování aplikací pomocí Visual Studia

Používání unit testů je bezesporu naprosto nezbytným základem pro psaní kvalitního softwaru. Zanedbání nebo naprostá absence testů (velmi častý jev u malých a středních projektů) má za následek problémy do budoucna, pokud software dále rozšiřujete.

Například po několika letech vývoje s velkou spoustou pluginů a způsobů využití je nejenom pro "nováčka" na takovém projektu cestou do pekel. Sebemenší úprava (rozšíření, optimalizace) jsou pak ruskou ruletou bez možnosti snadného ověření funkčnosti celku.

Na druhou stranu psaní a udržování testů může často trvat i mnohem déle než samotné psaní kódu. Proto je potřeba zvážit možné problémy a přínosy a zvolit, zda je testování vhodné a případně do jaké míry.

Jako jednoznačnou výhodu vidím, že můžete ošetřit svůj kód, proti zásahům ostatních v týmu (a také sebe samým) - pokud nevědomky zmodifikují nějakou funkčnost, na testu jasně vidí, kde nastal problém (který zprvu nemusí být vůbec patrný).

Příkladem jest jednoduchý zásah do jádra aplikace. Zprvu jednoduchá úprava může způsobit řadu problémů. My víme, že ty problémy můžou nastat, ale bez testů nemáme možnost cíleně kontrolovat všechny možné ovlivněné "komponenty".

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Mám naprosto stejnej názor a myslim si, že v případě nové kompilace není programátor schopnej otestovat všechny možnosti na které může mít změna dopad. Možná si myslím, že pokud se unitTest napíše dobře ve výsledku to může být i časově výhodnější.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Základy testování aplikací pomocí Visual Studia

Souhlasím s tím, že Unit Testing je dobrá a užitečná věc, ale u skutečně rozsáhlých a komplexních projektů mohu z vlastní zkušenosti říct, že psaní testů by dalo mnohem více práce než psaní samotného software.

nahlásit spamnahlásit spam 4 / 4 odpovědětodpovědět

U velkých projektů je Unit Testing (nejen) zcela zásadní, protože práve protože jsou to velké projekty, pracuje na nich zpravidla velký počet vývojařů (vetšinou různých kvalit). Každý zásah do kódu, který může ovlivnit různé části aplikace (a to se stává poměrně často), mívá fatální dopady (a bugfixing o nocích a o víkendech). Proto jsem přesvědčen, že ve většině případů se investice do vytvoření unit testů rozhodně vyplatí.

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět

To je tak možná zbožné přání, ale v realitě prostě na ty testy není čas. Má-li se dosáhnout do určitého termínu určitá funkčnost, je to i bez testů jen tak tak v malém nebo středně velkém týmu. To jsou moje praktické zkušenosti. Navíc ty testy jsou prakticky k ničemu pro testování uživatelského rozhraní (je potřeba testovat funkčnost celé aplikace) a v realitě slouží spíše k udržení integrity celého projektu, tj. když tam někdo něco dodělá, tak musí být zajištěno že tím nenaruší něco jiného.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

To říká hodně lidí, že na to není čas. Ve skutečnosti se každý kód, který napíšete, nějak testuje. I když nepíšete Unit Testy, tak se ten kód stejně testuje - prostě to spustíte a vidíte, jestli to funguje. Pokud aplikaci mám 10x spustit a podívat se, jestli funguje, jestli jsem úpravou jinde nenarušil to, co už fungovalo, tak se ten unit test už většinou vyplatí napsat. Záleží samozřejmě hodně na konkrétní situaci a na velikosti projektu, u malých projektů to opravdu ztráta času je.

Rozhodně nemá cenu psát unit testy na každou metodu a třídu, to je zbytečné a k ničemu. Je také potřeba říct, že většina aplikací, která dnes vzniká, je jen chytřejší a hezčí interface k databázi, takže tam se toho taky zas až tolik testovat nedá a nemusí.

Testovat uživatelské rozhraní je vhodné podle mě většinou nějakými stress testy (náhodné vyplňování komponent a klikání na tlačítka), aby se člověk ujistil, že mu to někde nepadá. I ty web testy ve Visualku jsou sice hezké, ale nikdy jsem je nepoužil, je s tím moc sena a proklikat tu aplikaci zas tolik práce nedá. Web test navíc nezachytí všechno. Asi jsem ještě nedělal hodně velký webový projekt, kde by se to hodilo a bylo efektivní.

Velkou výhodu unit testů vidím, když začínám nějaký větší projekt od začátku - prvních pár dní prostě píšu kód, který nemám jak testovat, není tam co spustit, interface dělávám většinou až na konec, nejdřív se zaměřuji na takové to funkční jádro. Pak se mi unit testy docela hodí, aniž bych to mohl "proklikat" v GUI, vím, že tahle metoda bude asi správně. Je to i takový psychologický bonus - člověk té aplikaci víc věří. Píšu to hlavně u metod, které dělají něco složitějšího, než např. "najdi něco v kolekci a něco tomu nastav".

Ještě k tomu času, záleží, jak moc se projekt v budoucnu rozroste. Pokud se píše už ze začátku s unit testy, tak ta prvotní časová investice je docela velká. Pokud se na aplikaci ale dělá víc než rok (někdo říká tři čtvrtě roku), pak se unit testy začnou hodně vyplácet. Někteří zákazníci (třeba banky) navíc testy požadují a nemají problém si za ně zaplatit.

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Jenže pak jsou prostě zákazníci, kteří nechtějí dát o +-30% víc peněz a času, aby měli aplikace dokonale otestovanou. Ono napsat test, který pokryje vše potřebné je občas hodně komplikované.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

To samozřejmě záleží na zákazníkovi a na tom, jaká je s ním domluva. Oni všichni lidi tak nějak předpokládají, že v programech chyby nejsou. Chirurg taky chybu udělat nesmí, tak jaktože programátor jo? Takovýmhle lidem se to pak blbě vysvětluje.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Pokud ale negunguje nějaká komponenta v IS, tak s tím zákazník může až do opravy žít (pokud si tuto levnější cestu zvolil - může to pro něj být výhodnější, pokud to není systém pro 200 zaměstnanců, kteří bez něj nemůžou fungovat). Se špatnou operací to je horší :-)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Základy testování aplikací pomocí Visual Studia

Abych řekl pravdu, unit testy jsem nikdy nepoužíval a říkal jsem si, k čemu to vlastně je, kde je ten přínos.

Nedávno jsem měl trochu času, tak jsem zkusil xUnit.net, který mi svým pojetím vyhovuje asi nejvíc a můj pohled na věc se trochu změnil. U většiny projektů stále zastávám názor, že unit testy jsou jen ztrátou času a že navíc podporují lenost a nepozornost vývojářů, ale jsou i projekty (resp. jejich části), kde mohou být testy jistě nepostradatelným pomocníkem.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback