IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

Mockito - unit test framework

Rád by som týmto článkom naviazal na zaujímavý článok ohľadom unit testov v Jave od Mateja.

Rád vám predstavím ďalšiu často používaný framework pre písanie unit testov v Jave a tým je Mockito.

Mockito slúži pre mockování tried. (Pr. Bean vo spring) Mockování je proces, kedy nie je volaná konkrétnej inštancie danej triedy, ale jej Mock.

Mock je náhrada za reálny objekt pre získavanie rôznych informácií o volaní daného objektu (pr. Počet volaní nejaké jeho metódy ...) Celý princíp je založený na návrhovom vzore proxy.

Sedláčka povedané: reálny objekt nahradíte objektom, ktorý zbiera štatistiku o objekte.

Postupne si prejdeme jednotlivé časti frameworku Mockito:

Runner

  • spúšťa testovacie triedy, píše sa pomocou anotácie nad názov triedy
  • až JUnit5 bude vedieť spúšťať viac runerov v jednej testovacej triede
@RunWith(MockitoJUnitRunner.class)

spustí MockitoAnnota­tions.initMoc­ks (this) a nainicializuje kontext s mock komponentmi.

Zapne používanie @Mock:

@RunWith(PowerMockRunner.class)

silnejšie ako predošlí runner, dovoľuje Mocková aj statická dáta (static).

Skôr než ak ho použijete radšej sa zamyslite, či je váš návrh správny!

Anotácie

@Mock vytvorí Mock z daného objektu objektom (proxy)

@InjectMock vykoná injekciu na základe typu a vytvorí testovateľné inštanciu

@Spy vlezie priamo do inštancie - dobré po počítanie času v metóde a dedičnosť. Opäť na Vás ale apelujem - ak musíte použiť @Spy, zvážte predtým zmenu návrhu!

@Spy
private EntityService entityService = new EntityServiceImpl();
doReturn(null).when(entityService).findByName(anyString());

WHEN vracia hodnoty (výnimka), ak je zavolaná daná metóda z Mock objektu.

základné volania:

when(carDao.findAll()).thenReturn(new ArrayList<Car>());

pre metódy, čo vracia void:

doThrow(IllegalStateException.class).when(carDao).create(Mockito.any(Car.class));

volanie danej metódy viackrát (pri prvom volaní vráti car, pri ďalšom vyhodí výnimku):

when(carService.findOne(any(Long.class)))
.thenReturn(car)
.thenThrow(IllegalStateException.class

MOCKITO MATCHERS zástupky za konkrétnej inštancie

matcher vysvetlenie
any () zodpovedá typu inštancie
eq () zodpovedá danej inštanciu
anyLong () zodpovedá typu Long
gt () väčšia ako ...
startsWith () začína na ...
intThat () vracia int hodnotu
argThat () pre vlastné matcher
pr .: intThat (is (greaterThan (9000))) - hodnota je väčšia ako 9000

VERIFY kontroluje volanie metódy v Mock objekte (koľkokrát bola volaná, ...)

základné volanie (metóda bola zavolaná raz):

verify(carDao).delete(Mockito.eq(id));

kontrola počtu volaní (metóda bola zavolaná dvakrát):

verify(carService, times(2)).findAll(any(Car.class));

kontrola nezavolaní metódy:

verify(carService, Mockito.never()).create(Mockito.any(Attribute.class));

kontrola nie viacero volanie. Skontroluje, že už viackrát nebolo volané:

verifyNoMoreInteractions(CarDao);

Captor dokáže odchytnout hodnoty pri volanie metódy vnútri testované metódy

// inicializace
ArgumentCaptor<type> captor = ArgumentCaptore.forClass(type);

// použití
captor.capture() //odchytne hodnotu
captor.getValue() //vrátíodchycenou hodnotu

príklad:

ArgumentCaptor<Car> carCaptor = ArgumentCaptor.forClass(Car.class);
when(carDao.create(carCaptor.capture())).thenReturn(car);
hasSetIdentNumber(carCaptor.getValue());

Špecialitky

kontrola poradie volania service:

InOrder inOrder = Mockito.inOrder(changeStrategy, noChangeStrategy);
inOrder.verify(changeStrategy).execute(eq(car));
inOrder.verify(noChangeStrategy).execute(eq(car));

kontrola hodnôt (Captor) dvojného priechodu jednej metódy:

ArgumentCaptor<Car> carCaptor = ArgumentCaptor.forClass(Car.class);
verify(carDao, times(2)).create(carCaptor .capture());
hasSetIdentNumber(carCaptor.getAllValues().get(0));
hasSetIdentNumber(carCaptor.getAllValues().get(1));

Poďme sa pozrieť na príklad z praxe

Zadanie:

Chceme si evidovať záznamy o ceste a prípadné finančné zľavy na rôzne cesty. Pre zjednodušenie pôjde o otestovanie metódy pre ukladanie záznamu o ceste.

Analýza:

Doménový objekt Journey reprezentuje cestu. Cesta je z nejakého miesta na nejaké miesto v daný čas a stála x peňazí (€).

public class Journey {
    private Long id ;
    private String from ;
    private String to ;
    private LocalDateTime dateTime ;
    //in Czech Crone
    private BigDecimal price = BigDecimal. ZERO ;

    //gettry, settry, equals, hashCode...

Trieda JourneyDao sa stará o prácu s databázou pre objekt Journey. V našom prípade slúži tiež pre vytvorenie Mock.

public interface JourneyDao {
    void save(Journey journey);

Trieda BonusService sa stará o prácu s bonusmi (cestovné zľavy). V našom prípade slúži tiež pre vytvorenie Mock.

public interface BonusService {
    /**
    * calculates bonus
    *
    * @param from city
    * @param to city
    * @return special bonus in this
    */
    BigDecimal getBonus(String from, String to

Najdôležitejšie je JourneyService, ktorá predstavuje testovanú triedu. Obsahuje jedinú metódu create (), ktorá vypočíta bonus za cestu a uloží ju do databázy.

public class JourneyServiceImpl implements JourneyService {
    @Autowired
    private JourneyDao journeyDao;
    @Autowired
    private BonusService bonusService;

    public void save(Journey journey) {
        BigDecimal bonus = bonusService.getBonus(journey.getFrom(), journey.getTo());

        if (bonus != null ) {
            journey.addBonus(bonus);
        }

        journeyDao.save(journey);
    }

Samotný test s využitím Mockita

Testovacie trieda obsahuje dva testy (s bonuse, bez bonusu). V jednom teste bonus nie je nájdený, v druhom teste je bonus nájdený.

//inicializuje MOCKITO
@RunWith (MockitoJUnitRunner.class)
public class SaveJourneyTest {
    //testovaná třída
    @InjectMocks
    private JourneyService journeyService = new JourneyServiceImpl();

    //vytvoření mock objektu
    @Mock
    private JourneyDao journeyDao ;
    @Mock
    private BonusService bonusService ;

    @Test
    public void whenSaveJourneyWithoutBonus_thenCalculatePriceWithoutBonus() {
        Journey journey = JourneyFactory.createDefaultJourney();
        // výsledek by měla být nezměněná cena, neboť neexistuje žádný bonus
        BigDecimal expected = new BigDecimal(
        JourneyFactory.DEFAULT_PRICE.doubleValue());
        // neexistuje bonus: jeli metoda getBonus volána, vrací null
        when (bonusService.getBonus(
            eq(JourneyFactory.DEFAULT_FROM ),
            eq(JourneyFactory.DEFAULT_TO )))
            .thenReturn(null);

        //testovaní metody
        journeyService.save(journey);

        //kontrola správanosti výsledků
        checkCalling(expected);
    }

    @Test
    public void thenSaveJourneyWithBonus_thenCalculatePriceWithBonus() {
        Journey journey = JourneyFactory.createDefaultJourney ();
        //tentokráte musí výsledek být i s bonusem
        BigDecimal expected = new BigDecimal(
        JourneyFactory.DEFAULT_PRICE.subtract(
        JourneyFactory.DEFAULT_BONUS).doubleValue());
        //po zavolání getBonus je poslát uživateli hodnota bonusu
        when(bonusService.getBonus(
            eq(JourneyFactory.DEFAULT_FROM),
            eq(JourneyFactory.DEFAULT_TO)))
            .thenReturn(JourneyFactory.DEFAULT_BONUS);

        journeyService.save(journey);

        checkCalling(expected);
    }

    // skontroluje správnost výsledku: správné volání metod a uložení správné ceny
    private void checkCalling(BigDecimal expected) {
        //kontrola, že methoda pro získání bonusu byla vůbec zavolána
        verify(bonusService).getBonus(anyString(), anyString());

        // odchytnem si objekt, který ve výsledku jde do uložení
        ArgumentCaptor<Journey> journeyCaptor = ArgumentCaptor.forClass(Journey.class);
        verify (journeyDao).save(journeyCaptor.capture());
        // skontrolujem, že byla na objektu nastavena správná cena
        assertEquals (expected, journeyCaptor.getValue().getPrice());

        // kontrola, že více toho nebylo zavoláno, než bylo potřeba
        verifyNoMoreInteractions(bonusService, journeyDao);
    }
}

Priložený súbor obsahuje tento posledný príklad.

Záverom ešte pár všeobecných rád na písanie testov:

  1. píšte krásnu rozprávku a nie bibliu, čo má veľa výkladu - Váš kód po Vás bude niekto čítať (i testy), snažte sa mu to pochopenie uľahčiť
  2. testujte všetku "zložitú" logiku - Slovíčko zložitú je na zváženie, ja osobne jednoriadkové metódy netestuje
  3. unit test by mal testovať čiernu skrinku, nie integráciu - Ak testovaná trieda využíva inú triedu, použite Mock objekt
  4. píšte krásne mená testov - Až po Vás niekto test bude čítať z názvu by mal vedieť, čo test robí
  5. nerobte moc dlhé testovacie triedy, rolovanie nemá nikto rád - Viac testov možno rozdeliť do viacerých tried

To je pre dnešok všetko - ďakujem za pozornosť.

V budúcej lekcii, Java spustenie - JVM argumenty , si predvedieme využitie JVM parametrov.


 

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 30x (7.28 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java

 

Všetky články v sekcii
Java - Pre pokročilých
Preskočiť článok
(neodporúčame)
Java spustenie - JVM argumenty
Článok pre vás napísal Petr Kunčar
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Nejlepší práce je taková, která vás baví. Nejlepší manželka je taková, co vás chápe. Nejlepší rodina je taková, co vás podporuje. Nejlepší relax je v přírodě. Nejlepší, co pro svět můžeš udělat, je řešit problémy rychle a elegantně.
Aktivity