Obiekty pozorujące¶
Ważne
Zewnętrzna zależność to obiekt w systemie, z którym komunikuje się testowany obiekt. Takie zależności zwykle trudno kontrolować (np. zależność od systemu plików, wątki, czas, połączenia sieciowe).
Ważne
Obiekt pozorujący (test double) jest kontrolowalnym zamiennikiem dla istniejącej zależności w systemie.
Obiekty pozorujące są alternatywnymi implementacjami interfejsów lub klas, których nie chcemy używać w teście, ponieważ:
- Są zbyt wolne
- Nie są dostępne (lub jeszcze nie istnieją)
- Zależą od zasobów, które w chwili uruchamiania testów nie są dostępne - np. połączenie sieciowe
- Trudno jest stworzyć ich instancję lub je skonfigurować dla celów testu
Załóżmy, na przykład, że istnieje metoda, która potrzebuje instancji PricingService
w celu wykonania obliczenia
sumy zamówienia.
Jeśli PricingService
korzysta z bazy danych, to wykonanie metody get_discount_percentage()
może zająć dużo czasu.
Nie chcemy używać w testach prawdziwej klasy, jeśli wykonanie każdego testu będzie wymagać połączenia z bazę danych.
Mogą pojawić się kolejne problemy, jeśli klasa PricingService
:
- będzie wymagać podania wielu parametrów do konfiguracji w teście
- nie została jeszcze zaimplementowana
Problemy z testowalnością obiektów mogą również wynikać z niedeterministycznego zachowania, np:
- braku dostępu do zasobów
- implementacji funkcjonalności zależnej od czasu
Często trudno jest spowodować wyjątki z użyciem prawdziwych obiektów w testach. Na przykład, wyłączenie i włączenie kabla sieciowego z testu jednostkowego w celu wywołania błędu dostępu do sieci.
Aby odizolować klasę OrderProcessor
od zależności od bazy danych możemy podstawić do testu obiekt pozorujący:
Dla klasy:
class PricingService
{
public:
virtual ~PricingService() = default;
virtual double get_discount_percentage(const Customer& c, const Product& p)
{
// real implementation
}
};
wprowadzamy implementację obiektu pozorującego:
struct PricingServiceTestDouble : PricingService
{
double discount;
explicit PricingServiceTestDouble(double discount) : discount(discount)
{}
double get_discount_percentage(const Customer& c, const Product& p) override
{
return discount;
}
};
i wykorzystujemy go w teście:
TEST(OrderProcessor, WhenProcessingOrderDiscountForCustomerIsCaclulated)
{
// arrange
double initial_balance = 100.0;
double list_price = 30.0;
double discount = 10.0;
double expected_balance = initial_balance - list_price * (1 - discount/100.0);
Customer customer{1, initial_balance};
Product product("TDD", list_price);
PricingServiceTestDouble service(discount); // creating a test double
OrderProcessor processor(service); // sut
Order new_order(customer, product);
// act
auto processed_order = processor.process(new_order);
// assert
ASSERT_EQ(processed_order.customer.balance, expected_balance);
}
W przykładzie pokazano, jak można podstawić naśladującą implementację klasy abstrakcyjnej PricingService
do testowanej klasy OrderProcessor
.
Dzięki temu pomijamy kosztowny krok połączenia z bazą danych oraz unikamy wykonywania długotrwałych obliczeń związanych z zachowaniem klienta, które nie są istotne z punktu widzenia testu.
Rodzaje obiektów pozorujących¶
W testach jednostkowych rozróżniamy kilka rodzajów obiektów pozorujących:
Typ obiektu | Opis |
---|---|
Stub | Najprostsza implementacja interfejsu. Metody stub’a zwykle zwracają zakodowane na sztywno wartości. |
Fake | Bardziej zaawansowana konstrukcja niż stub. Alternatywna implementacja interfejsu. Fake wygląda i działa jak prawdziwy obiekt, a stub tylko wygląda. |
Mock | Najbardziej zaawansowany obiekt pozorujący. Mock używa asercji do sprawdzenia oczekiwanej współpracy z innymi obiektami w czasie testu. W zależności od implementacji, może zwracać zakodowane na sztywno wartości lub dostarczać naśladujące implementacje logiki. Zwykle jest generowany za pomocą odpowiednich frameworków i bibliotek takich jak gmock, ale może być również implementowany ręcznie. |