Google Mock – definiowanie zachowań i oczekiwań w testach jednostkowych
Framework Google Mock (GMock) stanowi kluczowy komponent ekosystemu GoogleTest dla języka C++, umożliwiający tworzenie dynamicznych atrap (mock objects) w testach jednostkowych. Atrapy implementują interfejsy rzeczywistych obiektów, pozwalając na precyzyjne sterowanie zachowaniami oraz walidację interakcji. Niniejszy artykuł omawia koncepcję deklarowania zachowań za pomocą ON_CALL oraz definiowania oczekiwań poprzez EXPECT_CALL, wraz z praktycznymi zastosowaniami i potencjalnymi pułapkami.
Deklarowanie klas atrap
Podstawą pracy z GMock jest definicja klasy atrapy z wykorzystaniem makra MOCK_METHOD. Składnia wymaga określenia:
- Typu zwracanego,
- nazwy metody,
- listy argumentów (w nawiasach),
- opcjonalnych kwalifikatorów (np.
const,override,noexcept).
Przykład deklaracji atrapy dla interfejsuTurtle:
class MockTurtle : public Turtle {
public:
MOCK_METHOD(void, PenUp, (), (override));
MOCK_METHOD(void, PenDown, (), (override));
MOCK_METHOD(void, Forward, (int distance), (override));
};
Kwalifikator override jest zalecany przy nadpisywaniu metod wirtualnych, zaś const jest obowiązkowy dla metod zadeklarowanych jako stałe. Ewolucja składni pozwala również na obsługę referencji (ref(&)/ref(&&)) oraz specyfikatorów wyjątków.
Definiowanie zachowań z ON_CALL
Makro ON_CALL służy do deklarowania domyślnych zachowań atrapy, niezależnych od ścisłych oczekiwań testowych. Składnia:
ON_CALL(mock_object, method_name(matchers))
.WillByDefault(action);
- Matchery – określają warunki wywołania (np.
::testing::_dla dowolnego argumentu,::testing::Gt(5)dla wartości > 5); - Akcje – definiują reakcję atrapy (np.
Return(42),Throw(error)).
Przykład ustawienia domyślnej wartości zwracanej:
ON_CALL(myMock, GetSize())
.WillByDefault(Return(100));
ON_CALL jest szczególnie użyteczny dla metod wywoływanych wielokrotnie lub w przypadku, gdy test skupia się na logice nie związanej z wszystkimi możliwymi scenariuszami. Działa globalnie na poziomie obiektu atrapy, co może prowadzić do konfliktów z EXPECT_CALL (o czym poniżej).
Definiowanie oczekiwań z EXPECT_CALL
Podstawowym mechanizmem weryfikacyjnym GMock jest EXPECT_CALL, który:
- Deklaruje wymaganie wywołania metody;
- określa warunki (argumenty, liczba wywołań);
- definiuje reakcje dla spełnionych warunków.
Pełna składnia:
EXPECT_CALL(mock_object, method_name(matchers))
.Times(cardinality) // oczekiwana liczba wywołań
.InSequence(sequences...) // kolejność wywołań
.WillOnce(action) // akcja dla pierwszego pasującego wywołania
.WillRepeatedly(action); // akcja dla kolejnych wywołań
Kluczowe klauzule
- Cardinality –
Times(n)– dokładnienwywołań,AtLeast(n)/AtMost(n)– zakres wywołań,- pominięcie
Timesoznacza:EXPECT_CALL(mock, Foo()) // Times(1) - domyślnie EXPECT_CALL(mock, Bar()) .WillOnce(Return(1)) // Times(1) - z jednym WillOnce .WillOnce(Return(2)); // Times(2) - dwa WillOnce - Akcje –
WillOnce– akcja wykonana raz dla pasującego wywołania,WillRepeatedly– akcja dla wszystkich kolejnych wywołań.
Przykład sekwencyjnego sterowania zwracanymi wartościami:
EXPECT_CALL(turtle, GetX())
.WillOnce(Return(100)) // pierwsze wywołanie: 100
.WillOnce(Return(150)) // drugie wywołanie: 150
.WillRepeatedly(Return(200)); // kolejne: 200
Konflikty i interakcje między ON_CALL a EXPECT_CALL
Problem – gdy ON_CALL i EXPECT_CALL konkurują dla tych samych wywołań, pierwszeństwo ma EXPECT_CALL. Jednak każde wywołanie niespełniające warunków EXPECT_CALL (np. inny argument) zostanie przekierowane do ON_CALL i oznaczone jako nieoczekiwane, generując błąd.
Przykład problematyczny:
ON_CALL(mock, Example(_)).WillByDefault(Return(0)); // domyślnie 0
EXPECT_CALL(mock, Example(Ge(10))) // oczekiwanie dla ≥10
.WillRepeatedly(Return(5));
// Test:
FunctionUnderTest(15); // spełnia EXPECT_CALL → OK
FunctionUnderTest(3); // niespełnia EXPECT_CALL, używa ON_CALL → BŁĄD!
Rozwiązania –
- Użyj
NiceMocklubStrictMockdla kontroli zgłaszania nieoczekiwanych wywołań; - unikaj nadmiarowego
ON_CALLw obecnościEXPECT_CALLz wąskimi warunkami; - definiuj
EXPECT_CALLdla wszystkich przewidywanych wywołań, nawet trywialnych.
Zaawansowane techniki i best practices
Matchery argumentów
Matchery pozwalają na elastyczne dopasowywanie argumentów:
_– dowolna wartość,Gt(n)/Lt(n)– porównania,Eq(value)– równość,- złożone kombinacje (
AllOf,AnyOf).
Przykład:
EXPECT_CALL(api, Send(AllOf(Gt(100), Lt(200))))
.WillOnce(Return(SUCCESS));
Kontrola kolejności wywołań
Sekwencje wymuszają kolejność:
Sequence s1;
EXPECT_CALL(mock, A()).InSequence(s1);
EXPECT_CALL(mock, B()).InSequence(s1); // B wywołane po A
Dla niezależnych grup:
SequenceSet ss;
auto s1 = ss.AddNew(); // sekwencja 1
auto s2 = ss.AddNew(); // sekwencja 2
EXPECT_CALL(mock, C()).InSequenceSet(ss); // po wszystkich
Pułapki i zalecenia
- Kolejność deklaracji –
EXPECT_CALLmusi poprzedzać wywołania kodu; - Undefined behavior – zmiana oczekiwań (
EXPECT_CALL) po wywołaniu metody jest niezdefiniowana; - Nadmiarowe wywołania – użyj
RetiresOnSaturation()dla oczekiwań, które nie powinny być używane wielokrotnie; - Izolacja – każdy test powinien definiować własne oczekiwania, unikając stanu globalnego.
Podsumowanie i wnioski
ON_CALL i EXPECT_CALL są fundamentami kontroli zachowań obiektów atrap w GMock. Podczas gdy ON_CALL definiuje domyślne reakcje, EXPECT_CALL służy do weryfikacji specyficznych interakcji z atrapą. Kluczowe różnice:
| Cecha | ON_CALL |
EXPECT_CALL |
|---|---|---|
| Cel | Domyślne zachowanie | Walidacja wywołań |
| Wymagalność | Opcjonalny | Obowiązkowy dla weryfikacji |
| Kardynalność | Brak kontroli | Ścisła kontrola (Times()) |
| Priorytet | Niższy | Wyższy (nadpisuje ON_CALL) |
Praktyczne zalecenia:
- Używaj
ON_CALLostrożnie, gdy domyślne zachowanie jest naprawdę globalnie potrzebne; - preferuj
EXPECT_CALLdla jasnej ekspresji wymagań testowych; - unikaj mieszania
ON_CALLiEXPECT_CALLdla tych samych metod bez ścisłej kontroli warunków; - wykorzystuj matchery dla elastyczności i sekwencje dla złożonych scenariuszy.
Dzięki tej wiedzy programiści mogą efektywnie izolować testowany kod od zależności, tworząc niezawodne i utrzymywalne testy jednostkowe w C++.
