Android a pokrytí jednotkových testů

Tento článek pojednává o jednotkových testech (unit tests) a jejich použití v reálném Android projektu. Vysvětlím, proč je důležité takové testy psát a jaké zvolit nástroje. Budu předpokládat, že se čtenář s jednotkovými testy ještě nesetkal.

Proč testovat?

Ať už píšeme jakýkoliv software, měli bychom se snažit zajistit jeho kvalitu. To znamená, aby program dělal opravdu jen to, co se od něj očekává. U malých projektů jsme toto schopni ohlídat ručně (metodou kouknu a vidím), ale nesmíme zapomenout, že i z malého projektu se může časem stát velký projekt. A pak už jen těžko budeme procházet stovky řádků kódu a pamatovat si, že náš postup je správný. Nehledě na to, že s rozrůstajícím kódem přibývá počet stavů, ve kterých se software může nacházet.

Nedávno jsem četl moc pěkný článek, proč testovat software. Byl v něm uveden příklad vývoje a nasazení e-shopu, pro který se nepsaly téměř žádné testy. Až v produkci se zjistilo, že pokud není uživatel přihlášený jako admin nebo vývojář, není možné odesílat objednávky. A to je trochu průser, pokud se jedná o e-shop.

Jednotkové testy

Typů testování je několik druhů. V tomto článku se budu zabývat výhradně jednotkovými testy, které jsou určeny pro testování nejmenších programových celků (typicky to je funkce či metoda). Velmi známé jsou knihovny JUnit a TestNG. Osobně pracuji s JUnit. TestNG z ní vychází a snaží se přidat vlastnosti, které JUnit chybí, jako je například závislost mezi jednotlivými testy. Obě knihovny umožňují psát funkce, které ověří správnost jiné funkce. V testovací funkci programátor zadává očekávaný výstup a pokud volaná funkce vrátí něco jiného, test bude neúspěšný. Záleží tedy na programátorovi a jeho schopnostech ošetřit všechny možné případy, které mohou nastat.

Příkladem budiž funkce, která sčítá pole čísel. Při psaní testu musíme vzít v úvahu všechny možné vstupy. První test se bude provádět na to, jestli funkce pro čísla 1, 2, 3 vrací opravdu 6. Dalším testem bude to samé, ale pro záporná čísla. Atypickým vstupem může být prázdný vstup (tzn. na vstupu je pole new int[]). Dalším kandidátem na test by mohlo být zadání čísel, jejichž součet se nevejde do rozsahu Integer. Ve všech případech musí programátor vědět, jak se funkce zachová (a v testu psát očekávanou návratovou hodnotu).

Pro ověřování výsledku se používá funkce assert. Té se předává jako první parametr očekávaná hodnota a jako druhý parametr hodnota, kterou vrátila testovaná funkce. Pokud jsou hodnoty totožné, test projde. Pokud ne, test neprojde a je zastaven. Existuje hodně variant funkce assert, jako je např. assertNotNull, assertEquals, assertTrue apod.

Pokrytí jednotkových testů

Když už máme napsány všechny jednotkové testy, je dobré se ubezpečit, že jsme na žádný nezapomněli. Slouží k tomu nástroje na pokrytí testů, které označí ty řádky kódu, které se při testu vykonaly. Snadno tak zjistíme, že v projektu existuje funkce nebo její část, která nebyla testem nikdy zavolána. Pak už nám nebrání nic ve vytvoření nového jednotkového testu právě na toto hluché místo.

Za svou praxi jsem se setkal se dvěma nástroji, které pokrytí testů graficky znázorní. Jako první jsem začal používat utilitu Cobertura, která má plugin do Eclipse (eCobertura). Přímo v Eclipse zobrazí po ukončení testů zeleně ty řádky, které se provedly z nějakého testu a červeně ty, pro které neexistuje žádný test.

Druhým nástrojem je EMMA (plugin do Eclipse se jmenuje EclEmma). Funguje na stejném principu jako Cobertura a osobně v nich nevidím žádný velký rozdíl. Pro Android se mi ale Coberturu nepodařilo zprovoznit na 100%, takže používám právě EMMA. Hledal jsem na internetu, ale pro pokrytí jednotkových testů používají všichni Android vývojáři právě EMMA.

Reálný příklad Android projektu

Je zvykem vytvářet v projektu adresář src, ve kterém jsou umístěny zdrojové kódy aplikace, a adresář tests, kde jsou všechny testy. Je to možné i při programování pro Android, ale já preferuji vytvoření nového projektu s koncovkou Test. Výhodou je, že nemusím řešit jeden manifest pro dva projekty, drobnou nevýhodou pak oddělení testů od vlastního projektu (když stahuju zdrojové kódy, musím zvlášť pro projekt a zvlášť pro testy). Ale možná i to je pro někoho výhoda, když nemá o testy zájem, tak je jednoduše nestáhne 🙂

Vytvořme si tedy v Eclipse dva Android projekty. Jeden se bude jmenovat Pokus a druhý PokusTest, který bude testovacím projektem pro Pokus. V Eclipse (pokud máte nainstalovaný plugin ADT) stačí pravým tlačítkem kliknout na projekt Pokus a zvolit Android Tools -> New Test Project. Dalším krokem bude vytvoření testu, který ověří, zda aktivita MainActivity z projektu Pokus existuje.

Do testovacího projektu přidáme třídu, která obsahuje jediný test. Ten je pojmenovaný testMain. Protože používáme JUnit3, musí název každého textu začínat slovem test.

package cz.posvic.android.pokus.test;

import android.test.ActivityInstrumentationTestCase2;
import cz.posvic.android.pokus.MainActivity;

public class Basic extends ActivityInstrumentationTestCase2 {

	public Basic() {
		super("cz.posvic.android.pokus", MainActivity.class);
	}

	protected void setUp() throws Exception {
		super.setUp();
	}

	public void testMain() {
		MainActivity a = getActivity();
		assertNotNull(a);
	}
}

Co jsme vlastně vytvořili? Jedná se o testovací třídu, která dokáže přistupovat k aktivitě MainActivity nacházející se v projektu Pokus (ten je v balíčku cz.posvic.android.pokus). Jinak je to obyčejná třída. Můžeme například zvolit MainActivity jako třídní privátní proměnnou, pokud s ní chceme pracovat v jiných funkcích třídy. Samotný test získá aktivitu MainActivity. Do metody assertNotNull se předává výsledek získání aktivity a pokud je null, test neprojde.

Pokud chceme vidět pokrytí kódu jednotkovými testy, spustíme skript, který převede oba projekty pod Ant (pojmenoval jsem ho antify.sh). Spouští program z androidího SDK pro aktualizaci projektu. Ve výsledku se vytvoří několik souborů s konfigurací pro Ant.</>

#!/bin/bash

SDK=/home/phoenix/Programy/android-sdk-linux
PROJECT=/home/phoenix/Workspace/android

if [ $# -ne 1 ]; then
  echo "Usage: ./antify.sh ProjectName"
  exit 1
fi

echo -e "\033[1;32mAntifying $1...\033[0m"
$SDK/tools/android update project --path $PROJECT/$1 --name $1 --target android-8

echo -e "\033[1;32mAntifying $1Test...\033[0m"
$SDK/tools/android update test-project --main $PROJECT/$1 --path $PROJECT/$1Test

echo -e "\033[1;32mRun 'ant emma debug install test' in $PROJECT/$1Test\033[0m"

Jak říká sám skript, přesuneme se do testovacího projektu a spustíme příkaz ant emma debug install test, který se spojí s emulátorem a vytvoří v něm soubor s daty pro EMMA. Ten následně stáhne do počítače a vytvoří statistiku v adresáři PokusTest/bin/coverage.html.

Závěr

Jak sami vidíte, použití jednotkových testů je velmi triviální. Vygenerování pokrytí testů někdy také, v případě Androidu s tím je trochu potíž, ale není to nemožné. Každopádně určitě se vyplatí psát jednotkové testy. Nemusíte mít pokryto 100% kódu, ale hlavní a důležité části si pokrytí zaslouží. Ten čas, který nad psaním testů strávíte, ušetříte při opravách projektu, kdy jedna oprava obvykle přidává dvě nové chyby.

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *