Fake, Dummy, Stub, Mock, Test Double–krótkie wyjaśnienie terminologii używanej w testach jednostkowych

Czytając różne książki lub artykuły można się zgubić w terminologii. Szczególnie Mock oraz Stub często są używane zamiennie a ich różnica jest jednak znacząca. Nie tłumaczyłem na język polski powyższych terminów aby nie wprowadzać jeszcze większego zamieszaniaUśmiech

Zacznijmy od terminu najbardziej ogólnego – Double. Nazwa określa dowolny obiekt  naśladujący realną klasę. Zatem obiektem double może być Fake, Dummy, Stub lub Mock. Innymi słowy double można podzielić na wspomniane 4 obiekty. Termin stanowi po prostu ogólne określenie dowolnego obiektu, używanego podczas testów jednostkowych w celu izolacji. Nazwa wzięła się z filmów w których często pracę aktora w niebezpiecznych scenach zastępują dublerzy (kaskaderzy, stunt double).

Pierwszym typem Double jest Dummy, który stanowi najbardziej prymitywny sposób izolacji. Dummy używamy gdy nie ma dla nas znaczenia co dana klasa zrobi a po prostu zaprojektowana architektura wymagania przekazania jakiegoś obiektu. Wtedy tworzymy Dummy (atrapę), która po prostu nic nie robi. Dzięki temu będziemy mogli przetestować pozostałą funkcjonalność, izolując całkowicie  klasę, której nie chcemy weryfikować. Załóżmy, że mamy klasę Order:

interface IMessageBoxService
{
    Result Show(string message);
}
class Order
{
    private IMessageBoxService _messageBoxService;
    private IStockRepository _stockRepository;    
    public Product(IMessageBoxService msgService, IStockRepository stockRepository)
    {
        _messageBoxService = msgService;
        _stockRepository = stockRepository;
    }
    
    public bool Submit()
    {    
        msgService.Show("Proszę czekać - sprawdzanie zamówienia");
        
        int remainingQuantity=_stockRepository.GetQuantity(IdProduct);
        if( Quantity > remainingQuantity)
            return false;        
        
        // ... - jakas inna logika    
        
        msgService.Show("Gotowe!");
        return true;
    }
    // pozostala czesc logiki
}

W skrócie, za pomocą DI wstrzykujemy implementacje MessageBoxService oraz pewnego repozytorium służącego m.in. do sprawdzenia liczby sztuk towaru w magazynie. Następnie metoda Submit wyświetla dwa komunikaty oraz sprawdza czy żądana ilość towaru może zostać wydana. Oczywiście zadanie postawione to przetestowanie metody Submit. Załóżmy, że nie interesuje nas kompletnie to co robi MessageBoxService ponieważ jest to “tylko” warstwa prezentacji. Możemy użyć w tej sytuacji obiektu Dummy:

class DummyMsgBoxService: IMessageBoxService
{
    public Result Show(string message)
    {
        return Result.OK;
    }
}

Powyższa implementacja jest całkowitą atrapą – metoda nic tak naprawdę nie robi.

Kolejną, bardziej zaawansowaną konstrukcją jest obiekt Fake. W przeciwieństwie do Dummy, zawiera już pewną logikę jednak nie taką samą jak prawdziwy obiekt. W przypadku repozytorium, może używać zwykłą kolekcję danych (in-memory), zamiast odniesień do prawdziwej bazy danych.

Pozostały najbardziej mylone obiekty Stub i Mock. Stub używamy w testach jednostkowych wykorzystujących weryfikację stanu obiektów. Mock  z kolei używamy w testach opartych na badaniu zachowania (behaviour-based testing). Zacznijmy od krótkiego opisu testów opartych na stanie obiektów ponieważ są one najbardziej popularne. Stwórzmy więc Stub dla repozytorium:

class StubStockRepository: IStockRepository
{
    public int GetQuantity(int idProduct)
    {
        return 5;
    }
}

Jak widać, Stub zawsze zwraca z góry zdefiniowane wyniki. Następnie test dla Submit będzie wyglądać następująco:

public void SubmitTest()
{
    Order order=new Order(new StubStockRepository()){Quantity=10};
    bool result = order.Submit();
    
    Assert.IsFalse(result);
}

Test sprowadzana się zawsze do weryfikacji stanu obiektu lub zwróconej wartości. W testach z wykorzystaniem Stub wywołujemy metodę a następnie sprawdzamy właściwości klasy aby upewnić się, że wywołanie metody zmieniło odpowiednie pola w klasie. Z kolei sam Stub zwraca predefiniowaną przez nas, przewidywalną wartość.

Mock  opiera się na zachowaniach. Najpierw definiujemy nasze oczekiwania w formie jaka metoda powinna być wykonana, następnie wywołujemy testowaną metodę i sprawdzamy czy nasze oczekiwania zostały spełnione. Oczekiwania w przypadku metody Submit są następujące (przy założeniu, że w magazynie jest żądania ilość towaru):

  1. Metoda Show (IMessageBoxService) powinna zostać wywołana dwa razy.
  2. Metoda GetQuantity powinna zostać wywołana dokładnie raz. Ponadto w założeniu można zdefiniować przekazany parametr do GetQuantity – musi to być identyfikator produktu.

Z wykorzystaniem framework’u Moq, można test wykonać następująco:

[Test]
public void SubmitTest()
{
    var msgServiceMock = new Moq.Mock<IMessageBoxService>();
    var stockRepositoryMock = new Moq.Mock<IStockRepository>();
    
    Order order = new Order(msgServiceMock,stockRepositoryMock){Quantity = 3};
    order.Submit();

    // weryfikacja zdefiniowanych zachowań
    msgServiceMock.Verify(r => r.GetQuantity(Moq.It.IsAny()),Moq.Times.Once());
    stockRepositoryMock.Verify(m => m.Show(Moq.It.IsAny()),Moq.Times.Exactly(2));
    
}

Zamiast sprawdzania wyniku zwróconego przez Submit, weryfikujemy które metody zostały wykonane.  Możemy oczywiście uszczególnić i określić dokładnie jakie parametry powinny być przekazane, które metody nie powinny zostać wywołane oraz jakie operacje są dozwolone na właściwościach.

Więcej o testach typu behaviour, mam nadzieję, że napiszę wkrótce…

5 thoughts on “Fake, Dummy, Stub, Mock, Test Double–krótkie wyjaśnienie terminologii używanej w testach jednostkowych”

  1. Moim zdaniem takie mnożenie terminów jest bez sensu. Tym bardziej, że różnice pomiędzy nimi są… można powiedzieć “niewielkie”. Np dummy i stub – przecież to to samo.

    Sam staram się ograniczać do jednego: “mock”. Mock to implementacja utworzona (ręcznie bądź w runtime za pomocą mocking framework) na potrzeby izolacji testu od zewnętrznych zależności – i tyle.
    Kiedyś RhinoMocks zawierało API zaśmiecone wszystkimi różnymi tego typu nazwami: mock, dynamicmock, stub… na szczęście kolejne generacje tego typu bibliotek idą bardziej w stronę uproszczenia terminologii i redukowania zamieszania z nią związanego. Moq ma mocki, Fakeiteasy ma fake’i – a odpowiedzialne są za dokładnie te same zadania. I nie ma znaczenia “jak nazywa się”.

  2. To jest po prostu terminologia, ulatwiajaca np. czytanie pewnych ksiazek. Tak samo jak z wzorcami projektowymi – sa to nazwy na najbardziej popularne konstrukcje w kodzie. Czesc z wzorcow to naprawde proste konstrukcje a mimo wszystko maja swoja “specjalna” nazwe.
    Tak jak wspomnialem Fake, Dummy moze nie sa zbyt ciekawe. Ale z kolei Mock vs Stub imho wnosza juz cos wartosciowego.
    Zachecam do przeczytania dobrego tekstu Fowler’a:
    http://martinfowler.com/articles/mocksArentStubs.html

  3. Dobry artykuł – przedstawia spotykaną terminologię. Jednak również uważam, że klasyfikacja jest zbyt wybujała. Czy dana klasa zostanie określona Stub’em czy Mockiem zależy od jej użycia w danym tescie – kod klasy zwykle pozostaje dokładnie taki sam – wiec po co sobie utrudniać życie?

  4. Nie pozostanie taki sam. “Implementacja” stub’a rozni sie od mock’a przeciez:).

    Pewnie ze artykul wyjasnia terminologie, ale po co kazdy artykul ma nowa wymyslac skoro w inzynierii oprogramowania juz zostaly takowe zdefiniowane i to przez bardzo znane osoby (Fowler).

Leave a Reply

Your email address will not be published.