Testování jednotky - Unit testing

V počítačové programování , Unit testování je testování software způsob, jakým jednotlivé jednotky zdrojového kódu -sets jednoho nebo více počítačových programů modulů spolu s přidruženými kontrolními daty, použití postupů a operačních postupů, jsou testovány, aby se zjistilo, zda jsou vhodné pro použití .

Popis

Testy jednotek jsou obvykle automatizované testy napsané a spuštěné vývojáři softwaru, aby bylo zajištěno, že část aplikace (známá jako „jednotka“) splňuje svůj design a chová se tak, jak má. V procedurálním programování může být jednotkou celý modul, ale je to běžněji samostatná funkce nebo postup. V objektově orientovaném programování je jednotkou často celé rozhraní, například třída nebo individuální metoda. Napsáním testů nejdříve pro nejmenší testovatelné jednotky a poté složené chování mezi nimi lze vytvořit komplexní testy pro složité aplikace.

Abychom izolovali problémy, které mohou nastat, měl by být každý testovací případ testován samostatně. Jako pomoc při testování izolace modulu lze použít náhražky , jako jsou útržky metod , falešné předměty , padělky a testovací svazky .

Během vývoje může vývojář softwaru do testu kódovat kritéria nebo výsledky, o nichž je známo, že jsou dobré, aby ověřil správnost jednotky. Během provádění testovacích případů rámce protokolují testy, které nesplní jakékoli kritérium, a hlásí je v souhrnu. Nejčastěji používaným přístupem je testovaná funkce - očekávaná hodnota.

Zápis a údržbu testů jednotek lze zrychlit pomocí parametrizovaných testů . Ty umožňují provádění jednoho testu vícekrát s různými vstupními sadami, čímž se snižuje duplikace testovacího kódu. Na rozdíl od tradičních jednotkových testů, které jsou obvykle uzavřenými metodami a testují invariantní podmínky, provedou parametrizované testy jakoukoli sadu parametrů. Parametrizované testy jsou podporovány TestNG , JUnit a jeho protějškem .Net, XUnit . Vhodné parametry pro jednotkové testy mohou být dodány ručně nebo v některých případech jsou automaticky generovány testovacím rámcem. V posledních letech byla přidána podpora pro psaní výkonnějších (jednotkových) testů, využívajících koncept teorií, testovacích případů, které provádějí stejné kroky, ale používají testovací data generovaná za běhu, na rozdíl od běžných parametrizovaných testů, které používají stejné kroky provádění se vstupními sadami které jsou předdefinovány.

Výhody

Cílem testování jednotek je izolovat každou část programu a ukázat, že jednotlivé části jsou správné. Test jednotky poskytuje přísnou písemnou smlouvu, kterou musí část kódu splnit. Výsledkem je několik výhod.

Testování jednotek najde problémy na začátku vývojového cyklu . To zahrnuje jak chyby v implementaci programátoru, tak chyby nebo chybějící části specifikace jednotky. Proces psaní důkladné sady testů nutí autora přemýšlet o vstupech, výstupech a chybových podmínkách, a tak jasněji definovat požadované chování jednotky. Náklady na nalezení chyby před zahájením kódování nebo při prvním zápisu kódu jsou podstatně nižší než náklady na detekci, identifikaci a opravu chyby později. Chyby ve vydaném kódu mohou také způsobit nákladné problémy koncovým uživatelům softwaru. Testování kódu může být nemožné nebo obtížné, pokud je špatně napsáno, takže testování jednotky může vývojáře přinutit strukturovat funkce a objekty lepšími způsoby.

Ve vývoji řízeném testem (TDD), který se často používá jak v extrémním programování, tak ve scrumu , se vytvářejí testy jednotek před zápisem samotného kódu. Když testy projdou, považuje se tento kód za úplný. Stejné testy jednotek se proti této funkci spouštějí často, protože se vyvíjí větší kódová základna buď při změně kódu, nebo prostřednictvím automatizovaného procesu s sestavením. Pokud testy jednotky selžou, považuje se to za chybu buď ve změněném kódu, nebo v samotných testech. Testy jednotky poté umožňují snadné dohledání místa poruchy nebo poruchy. Vzhledem k tomu, že testy jednotky upozorní vývojový tým na problém před předáním kódu testerům nebo klientům, potenciální problémy se zachytí na začátku vývojového procesu.

Testování jednotek umožňuje programátorovi refaktorovat kód nebo upgradovat systémové knihovny později a ujistit se, že modul stále funguje správně (např. Při regresním testování ). Postupem je psaní testovacích případů pro všechny funkce a metody tak, aby kdykoli změna způsobila poruchu, mohla být rychle identifikována. Testy jednotek detekují změny, které mohou porušit konstrukční smlouvu .

Testování jednotek může snížit nejistotu v samotných jednotkách a lze je použít v přístupu stylu testování zdola nahoru . Nejprve otestováním částí programu a poté otestováním součtu jeho částí je testování integrace mnohem jednodušší.

Testování jednotek poskytuje jakousi živou dokumentaci systému. Vývojáři, kteří se chtějí dozvědět, jaké funkce poskytuje jednotka a jak ji používat, se mohou podívat na testy jednotek, aby získali základní znalosti o rozhraní jednotky ( API ).

Testovací případy jednotky zahrnují vlastnosti, které jsou rozhodující pro úspěch jednotky. Tyto vlastnosti mohou naznačovat vhodné / nevhodné použití jednotky i negativní chování, které má jednotka zachytit. Jednotkový testovací případ sám o sobě dokumentuje tyto kritické vlastnosti, ačkoli mnoho vývojových prostředí softwaru se při vývoji produktu nespoléhá pouze na kód.

Pokud je software vyvíjen pomocí přístupu založeného na zkoušce, může místo formálního návrhu nahradit kombinaci psaní testu jednotky k určení rozhraní plus činnosti refaktoringu prováděné po absolvování testu. Každý test jednotky lze považovat za designový prvek specifikující třídy, metody a pozorovatelné chování.

Omezení a nevýhody

Testování nezachytí každou chybu v programu, protože nemůže vyhodnotit každou cestu spuštění v žádném, ale v těch nejtriviálních programech. Tento problém je nadmnožinou problému zastavení , který je nerozhodnutelný . Totéž platí pro testování jednotek. Testování jednotek podle definice navíc testuje pouze funkčnost samotných jednotek. Proto nezachytí chyby integrace ani chyby širší systémové úrovně (například funkce prováděné napříč více jednotkami nebo nefunkční testovací oblasti, jako je výkon ). Testování jednotky by mělo být prováděno ve spojení s dalšími aktivitami testování softwaru , protože mohou ukázat pouze přítomnost nebo nepřítomnost konkrétních chyb; nemohou prokázat úplnou absenci chyb. Aby bylo možné zaručit správné chování pro každou cestu provedení a všechny možné vstupy a zajistit, aby nedocházelo k chybám, jsou vyžadovány další techniky, konkrétně použití formálních metod k prokázání, že softwarová součást nemá neočekávané chování.

Propracovaná hierarchie jednotkových testů se nerovná integračnímu testování. Integrace s periferními jednotkami by měla být zahrnuta do integračních testů, nikoli však do testů jednotek. Testování integrace se obvykle stále silně spoléhá na ruční testování lidmi ; testování na vysoké nebo globální úrovni může být obtížné automatizovat, takže ruční testování se často jeví rychlejší a levnější.

Testování softwaru je kombinatorickým problémem. Například každý výrok booleovského rozhodnutí vyžaduje alespoň dva testy: jeden s výsledkem „true“ a druhý s výsledkem „false“. Výsledkem je, že pro každý zapsaný řádek kódu programátoři často potřebují 3 až 5 řádků testovacího kódu. To samozřejmě vyžaduje čas a jeho investice nemusí stát za tu námahu. Existují problémy, které nelze vůbec snadno otestovat - například ty, které jsou nedeterministické nebo zahrnují více vláken . Kromě toho bude kód pro test jednotky pravděpodobně stejně chybný jako kód, který testuje. Fred Brooks v The Mythical Man-Month cituje: „Nikdy nechoďte na moře se dvěma chronometry; vezměte si jeden nebo tři.“ To znamená, že když si dva chronometry odporují, jak víte, který z nich je správný?

Další výzvou související s psaním jednotkových testů je obtížnost nastavení realistických a užitečných testů. Je nutné vytvořit relevantní počáteční podmínky, aby se testovaná část aplikace chovala jako součást celého systému. Pokud tyto počáteční podmínky nejsou nastaveny správně, test nebude cvičit kód v realistickém kontextu, což snižuje hodnotu a přesnost výsledků jednotkových testů.

K získání zamýšlených výhod z testování jednotek je během procesu vývoje softwaru nutná přísná disciplína. Je nezbytné pečlivě zaznamenávat nejen testy, které byly provedeny, ale také všechny změny, které byly provedeny ve zdrojovém kódu této nebo jakékoli jiné jednotky v softwaru. Použití systému pro správu verzí je zásadní. Pokud novější verze jednotky selže při konkrétním testu, který předtím prošel, může software pro správu verzí poskytnout seznam změn zdrojového kódu (pokud existují), které byly od té doby na jednotku použity.

Je také nezbytné zavést udržitelný proces zajišťující, že selhání testovacích případů budou pravidelně kontrolována a okamžitě řešena. Pokud takový proces není implementován a zakořeněn do pracovního postupu týmu, aplikace se vyvine synchronizovaně s jednotkovou testovací sadou, což zvýší falešné pozitivy a sníží účinnost testovací sady.

Integrovaný systémový software pro testování jednotek představuje jedinečnou výzvu: Protože se software vyvíjí na jiné platformě, než na které bude nakonec spuštěn, nemůžete snadno spustit testovací program ve skutečném prostředí nasazení, jak je to možné u desktopových programů.

Testy jednotek bývají nejjednodušší, když má metoda vstupní parametry a nějaký výstup. Vytváření testů jednotek není tak snadné, když hlavní funkcí metody je interakce s něčím externím vůči aplikaci. Například metoda, která bude pracovat s databází, může vyžadovat vytvoření makety databázových interakcí, které pravděpodobně nebudou tak komplexní jako skutečné databázové interakce.

Příklad

Zde je sada testovacích případů v Javě, které specifikují řadu prvků implementace. Nejprve musí existovat rozhraní s názvem Adder a implementační třída s konstruktorem s nulovým argumentem s názvem AdderImpl. Dále tvrdí , že rozhraní Adder by mělo mít metodu nazvanou add, se dvěma celočíselnými parametry, která vrací další celé číslo. Rovněž určuje chování této metody pro malý rozsah hodnot u řady testovacích metod.

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TestAdder {

    @Test
    public void testSumPositiveNumbersOneAndOne() {
        Adder adder = new AdderImpl();
        assertEquals(2, adder.add(1, 1));
    }

    // can it add the positive numbers 1 and 2?
    @Test
    public void testSumPositiveNumbersOneAndTwo() {
        Adder adder = new AdderImpl();
        assertEquals(3, adder.add(1, 2));
    }

    // can it add the positive numbers 2 and 2?
    @Test
    public void testSumPositiveNumbersTwoAndTwo() {
        Adder adder = new AdderImpl();
        assertEquals(4, adder.add(2, 2));
    }

    // is zero neutral?
    @Test
    public void testSumZeroNeutral() {
        Adder adder = new AdderImpl();
        assertEquals(0, adder.add(0, 0));
    }

    // can it add the negative numbers -1 and -2?
    @Test
    public void testSumNegativeNumbers() {
        Adder adder = new AdderImpl();
        assertEquals(-3, adder.add(-1, -2));
    }

    // can it add a positive and a negative?
    @Test
    public void testSumPositiveAndNegative() {
        Adder adder = new AdderImpl();
        assertEquals(0, adder.add(-1, 1));
    }

    // how about larger numbers?
    @Test
    public void testSumLargeNumbers() {
        Adder adder = new AdderImpl();
        assertEquals(2222, adder.add(1234, 988));
    }
}

V tomto případě test jednotky, který byl napsán jako první, funguje jako návrhový dokument specifikující formu a chování požadovaného řešení, ale ne podrobnosti implementace, které jsou ponechány programátorovi. V návaznosti na praxi „udělejte nejjednodušší věc, která by mohla fungovat“ je níže uvedeno nejjednodušší řešení, které testovací průchod provede.

interface Adder {
    int add(int a, int b);
}
class AdderImpl implements Adder {
    public int add(int a, int b) {
        return a + b;
    }
}

Jako spustitelné specifikace

Použití jednotkových testů jako specifikace návrhu má jednu významnou výhodu oproti jiným metodám návrhu: K ověření implementace lze použít samotný návrhový dokument (samotné jednotkové testy). Testy nikdy neprojdou, pokud vývojář neimplementuje řešení podle návrhu.

Testování jednotek postrádá část přístupnosti schematické specifikace, jako je diagram UML , ale mohou být generovány z testu jednotky pomocí automatizovaných nástrojů. Většina moderních jazyků má bezplatné nástroje (obvykle dostupné jako rozšíření IDE ). Bezplatné nástroje, jako ty založené na rámci xUnit , zadávají grafickému vykreslení pohledu pro lidskou spotřebu jinému systému.

Aplikace

Extrémní programování

Testování jednotek je základním kamenem extrémního programování , které závisí na automatizovaném rámci testování jednotek . Tento rámec pro automatické testování jednotek může být buď třetí strana, např. XUnit , nebo vytvořená v rámci vývojové skupiny.

Extrémní programování využívá vytváření testů jednotek pro vývoj řízený testy . Vývojář napíše test jednotky, který odhalí buď softwarový požadavek, nebo závadu. Tento test se nezdaří, protože požadavek ještě není implementován, nebo proto, že záměrně vystavuje defekt v existujícím kódu. Poté vývojář napíše nejjednodušší kód, aby test spolu s dalšími testy prošel.

Většina kódu v systému je testována jednotkou, ale ne nutně všechny cesty kódem. Extrémní programování nařizuje strategii „otestovat vše, co může zlomit“, nad tradiční metodou „otestovat každou cestu provedení“. To vede vývojáře k tomu, aby vyvinuli méně testů než klasické metody, ale to ve skutečnosti není problém, jde spíše o přepracování faktu, protože klasické metody byly zřídkakdy používány dostatečně metodicky, aby byly důkladně otestovány všechny cesty provádění. Extrémní programování jednoduše uznává, že testování je zřídka vyčerpávající (protože je často příliš nákladné a časově náročné na to, aby bylo ekonomicky životaschopné), a poskytuje návod, jak efektivně zaměřit omezené zdroje.

Rozhodující je, že testovací kód je považován za artefakt projektu první třídy v tom, že je udržován ve stejné kvalitě jako implementační kód, přičemž je odstraněna veškerá duplikace. Vývojáři vydávají testovací kód jednotky do úložiště kódu ve spojení s kódem, který testuje. Důkladné testování jednotek extrémního programování umožňuje výše uvedené výhody, jako je jednodušší a jistější vývoj a refaktorování kódu, zjednodušená integrace kódu, přesná dokumentace a více modulárních návrhů. Tyto jednotkové testy se také neustále spouštějí jako forma regresního testu .

Testování jednotky je také zásadní pro koncept Emergent Design . Jelikož vznikající design je silně závislý na refaktoringu, jsou jednotkové testy nedílnou součástí.

Jednotkové testovací rámce

Rámečky testování jednotek jsou nejčastěji produkty třetích stran, které nejsou distribuovány jako součást sady kompilátorů. Pomáhají zjednodušit proces testování jednotek, protože byly vyvinuty pro širokou škálu jazyků .

Obecně je možné provádět testování jednotek bez podpory konkrétního rámce psaním kódu klienta, který cvičí testované jednotky a k signalizaci selhání používá tvrzení , zpracování výjimek nebo jiné mechanismy toku řízení . Testování jednotek bez rámce je cenné v tom, že existuje překážka vstupu pro přijetí testování jednotek; mít nedostatečné jednotkové testy je stěží lepší než nemít vůbec žádné, zatímco jakmile je zaveden rámec, přidávání jednotkových testů je relativně snadné. V některých rámcích mnoho pokročilých funkcí testování jednotek chybí nebo musí být ručně kódováno.

Podpora testování jednotek na jazykové úrovni

Některé programovací jazyky přímo podporují testování jednotek. Jejich gramatika umožňuje přímou deklaraci jednotkových testů bez importu knihovny (ať už třetí strany nebo standardu). Navíc booleovské podmínky jednotkových testů lze vyjádřit ve stejné syntaxi jako booleovské výrazy používané v necelkových testovacích kódech, například co se používá pro ifa whilepříkazy.

Mezi jazyky s integrovanou podporou testování jednotek patří:

Některé jazyky bez integrované podpory testování jednotek mají velmi dobré knihovny / rámce testování jednotek. Mezi tyto jazyky patří:

Viz také

Reference

externí odkazy