Pětiminutové intro do Mockito knihovny
Pár lidí se mě ptalo na Mockito a po té co ji vyzkoušeli mi dali za pravdu, že se jedná opravdu o zásadní nástroj pro psaní testů. Není mým cílem popsat všechny vlastnosti této knihovny, ukážu pouze to nejzásadnější s pár drobnými komentáři. I tak čtenář neznalý této knihovny dostane do rukou návod, který mu vystačí na 80% případů, se kterými se střetne při testování.
Jedním z důvodu proč mají vývojáři odpor pro psaní testů je omáčka, kterou je potřeba vytvořit pro nastavení vlastního prostředí testovaného objektu a složitost verifikace výsledků samotného testu. To je přesně situace, ve které Mockito tahá kaštany z ohně.
Mockito slouží k mockování tříd a jejich chování a kontrolu jejich interakce s okolním světem. Neznám český překlad slova mock a mockování a dosti silně pochybuji, že vůbec existují. Mock je dynamicky vytvořený objekt s rozhraním dané třídy. Mockování je definice chování mocku při interakci s okolním světem, příklad volám na mocku metodu X, chci aby se vrátila hodnota 1.
Poznámka na okraj. Mockito mi pomohlo v pochopení, že kód který není dobře objektově navržený není možné efektivně otestovat. Kromě všech vlastností, kterými mi pomáhá při psaní testů, zároveň nastavuje zrcadlo objektovému návrhu a nutí mě přemýšlet jakým způsobem jej vylepšit, což zpětně vede k lepší testovatelnosti.
K jednoduché ukázce Mockita jsem si vybral dva objekty Basket a Item představující nákupní košík respektive zboží v něm uložené. Nákupní košík má metody pro přidání nového zboží, vyčištění košíku a vrácení celkové ceny vloženého zboží. Zboží má pro jednoduchost pouze metodu, která vrací jeho hodnotu.
public class Item {private final double price;
public Item(double price) {
super();
this.price = price;
}public double getPrice() {
return price;
}
}public class Basket {private final List items;
public Basket() {
this(new ArrayList());
}Basket(List items) {
super();
this.items = items;
}public Basket addItem(Item item) {
this.items.add(item);
return this;
}public List getItems() {
return items;
}public void clear() {
this.items.clear();
}public double getTotalPrice() {
double totalPrice = 0;
for (Item item : items) {
totalPrice += item.getPrice();
}
return totalPrice;
}
}Klíčovou třídou pro použití Mockita představuje org.mockito.Mockito. Ta má spoustu statických metod a vyplatí se proto udělat její statický import. Nejzásadnější metody jsou:
- Mockito.mock — jako argument dostává třídu, ze které vytváří mock (třída nesmí být final).
- Mockito.when — umožňuje definovat chování (vrať hodnotu, vyhoď vyjímku) při volání dané metody třídy
- Mockito.verify — umožňuje zkontrolovat, jestli daná metoda mocku byla volaná a to včetně argumentů, se kterými byla volaná
Na prvním příkladu si ukážeme jak otestovat očekávané chování metody pro celkovou cenu zboží uložené v košíku. Připravíme si dvě mock instance zboží, které vložíme do košíku.
public void testGetTotalPrice() {
Basket basket = new Basket();
Item item1 = mock(Item.class);
when(item1.getPrice()).thenReturn(10d);
Item item2 = mock(Item.class);
when(item2.getPrice()).thenReturn(20d);basket
.addItem(item1)
.addItem(item2);
assertThat("The total price must be sum of all prices in basket", basket.getTotalPrice(), is(30d));
}Při mockování zboží, představovaného třídou Item jsme nejdříve vytvořili mock a po té jsme určili chování metody getPrice. V tomto případě by bylo možné namítnout, že není důvod třídu Item mockovat a použít přímo její instanci. Osobně se snažím všude používat mocky z toho důvodu, že testovací třídy a metody jsou na sobě méně závislé.
Dalším testem chceme ověřit, že košík se při volání metody clear opravdu vyčistí. K tomuto účelu můžeme použít právě techniku ověření interakce s okolním světem. Já jsem zamockoval interní list, ve kterém si košík drží všechny položky a na něm ověřil volání jeho metody pro vyčištění.
@Test
public void testClear() {
List items = mock(List.class);
Basket basket = new Basket(items);
basket.clear();
verify(items).clear();
}
Poslední řádek právě verifikuje volání metody clear na mocku. Podobným způsobem se zamockováním interního listu lze otestovat i přidávání zboží do košíku. O tom, že je tato technika trochu kontroverzní se zmíním vzápětí.
@Test
public void testAddItem() {
Item item = mock(Item.class);
List items = mock(List.class);
Basket basket = new Basket(items);
basket.addItem(item);
verify(items).add(item));
}
Při používání mocků a verifikace jejich interakce s testovaným objektem má několik nevýhod. V případě, že začnete tuto verifikaci provádět, tak si ověřujete interní implementaci, která by měla být testu absolutně ukradená, pokud se bavíme o black box přístupu. Je na zvážení, co je potřeba ještě verifikovat, protože změna implementace může znamenat, že testy přestanou procházet jenom z toho důvodu, že byly vázané na implementační detaily. Další nevýhodou je fakt, že někdy musíte pro testy porušit zapouzdření a vystavit právě onu interní část, kterou je potřeba zamockovat.
Mockito toho umí daleko více než metody zmíněné v tomto článku. Můžete například verifikovat i argumenty se kterými se volá mock, můžete otestovat, že nedochází k interakci. Můžete dělat částěčně mocky. Každopádně základ práce s Mockitem pokrývají právě tyto tři metody.
Související články
Související epizody CZ Podcastu
- #261 · Leadership
O vedení lidí a týmů.
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi a Filemon o novinkách v Javě 6 a webových frameworcích.
- #1 · Vývojová prostředí v Javě
První díl CZ Podcastu se věnuje vývojovým prostředím pro Javu.