Blog
Pojęcie Test Double oznacza jakikolwiek komponent systemu, którego używamy jako specyficzny element w czasie testów systemu. Double to nazwa ogólna określająca całą rodzinę elementów. Wyróżniamy kilka rodzajów Double takich jak: Dummy, Fake, Stub, Mock i Spy. Aby było ciekawiej potrafią one być różnie interpretowane. Ponadto, nawet w oficjalnych publikacjach określane są różnie, element w jednej z publikacji określony jako Stub, w innej może być określony jako Fake lub Dummy. Poniżej przedstawię najbardziej uniwersalny podział.
Aby jednak nie zrażać się mogącymi występować różnicami w nazewnictwie – zaakcentuję fakt, że ważniejsze jest poznanie różnic w działaniu, które mogą pomóc nam dostrzec nowe możliwości podczas naszej pracy z testami, niż samo przyporządkowanie ich do konkretnych nazw.
Dummy
Dummy jest najprostszym z typów. Wyróżnia się tym, że w trakcie testów systemu (w dalszej części będziemy posługiwać się określeniem: System Under Test – SUT) nie korzysta bezpośrednio z danych wejściowych, ani nie weryfikuje danych wyjściowych. Dummy służy jako konieczny do wywołania – aczkolwiek dowolny, gdyż nie mający wpływu na wynik testu, placeholder. Przykładem Dummy może być null lub „Any string” przekazany w parametrze metody lub konstruktorze obiektu.
it("checks that person is adult", function () {
var person = new person("Any name", "Any surname", 21);
assert.equals(person.isAdult(), true);
});
W powyższym przykładzie „Any name” i „Any surname” to Dummy – są konieczne do skonstruowania obiektu, jednak ich wartość nie ma żadnego wpływu na wynik testu.
Stub
Stub jest używany w celu przekazania do SUT bezpośrednio danych, pochodzących w rzeczywistym systemie z zależnych obiektów. Dane te mogą być dowolnego typu. W odróżnieniu od Dummy w przypadku Stub ważny jest fakt że wynik testu jest zależny od danych w nim zwracanych.
function shieldPower(laserPower, boost) {
return laserPower() * boost();
}
describe("space ship shield", function() {
it("is set to value higher by secure level than enemy laser power", function () {
var laserPower = sinon.stub().returns(100);
var secureLevel = sinon.stub().returns(2);
expect(shieldPower(laserPower, secureLevel)).toBe(200);
});
});
W powyższym przykładzie wynik testu bezpośrednio zależny jest od zwracanej przez Stub wartości.
Fake
Fake służy do zmniejszenia zależności w systemie. Dzięki niemu możemy pozbyć się części zależności które mogą być niepotrzebne, mogą wprowadzać negatywny efekt mogący zaburzyć wynik testów lub nawet całkowicie uniemożliwić testowanie.
function shieldActivator(callback, time) {
var timer;
return function () {
clearTimeout(timer);
var args = [].slice.call(arguments);
timer = setTimeout(function () {
callback.apply(this, args);
}, time);
};
}
describe("space ship", function() {
var clock;
beforeEach(function () { clock = sinon.useFakeTimers(); });
afterEach(function () { clock.restore(); });
it("activates shield in expected time after attack", function () {
var time = 100;
var callback = sinon.spy();
var activate = shieldActivator(callback, time);
activate();
clock.tick(time - 1);
expect(callback.notCalled).toBe(true);
clock.tick(2);
expect(callback.calledOnce).toBe(true);
});
});
W powyższym przykładzie Fake służy do zasymulowania timera, dzięki niemu zmniejszamy zależności od timera – zastępując jego implementację własną. Przy czym SUT nie jest bezpośrednio zależny od danych zwracanych przez Fake – a jedynie używa jego implementacji do zmniejszenia zależności.
Mock
Mock funkcjonalnie rozszerza możliwości Stuba. Mock może opcjonalnie – tak samo jak i Stub zwracać dane od których zależny jest wynik testu. Jednakże to jest jedynie opcjonalny efekt dodatkowy. W przypadku Mocka jego głównym celem jest to że weryfikujemy bezpośrednio dane wyjściowe z SUT. Możemy weryfikować parametry z jakimi została wywołana metoda ale również ilość wywołań. Dzięki Mockom można zweryfikować fakt, że system wywołuje zależności w dokładnie taki sposób w jaki powinien.
function connectToNetwork(url) {
$.ajax(url);
}
describe("space ship", function() {
it("connects to global network", function () {
var url = "http://myurl.com";
var connectMock = sinon.mock(jQuery).expects("ajax").exactly(1).withExactArgs(url);
connectToNetwork(url);
connectMock.verify();
});
});
W powyższym przykładzie Mock weryfikuje że wywołanie metody ajax nastąpiło dokładnie jednokrotnie i z dokładnie jednym parametrem którym jest string „http://myurl.com”. W przypadku Mocka z góry definiujemy jakich wartości oczekujemy a weryfikacja odbywa się z użyciem tych danych.
Spy
Spy jest bardzo zbliżony do Mocka – jedyną różnicą między nimi jest fakt że w przypadku Spy nie definiujemy wcześniej jakich oczekujemy danych, zamiast tego przechwytujemy wynik działania w celu późniejszej weryfikacji.
Aby to zobrazować porównajmy więc Mock i Spy.
function runEngine(engine, fuel) {
return function() {
engine(fuel);
};
}
describe("space ship", function() {
var fuel = "any fuel";
//Spy
it("starts engine", function () {
var spy = sinon.spy();
var engine = runEngine(spy, fuel);
engine();
expect(spy.called).toBe(true);
expect(spy.getCall(0).args[0]).toBe(fuel);
});
//Mock
it("starts engine", function () {
var mock = sinon.mock().once().withExactArgs(fuel);
var engine = runEngine(mock, fuel);
engine();
mock.verify();
});
});
W powyższym przykładzie dla Spy przechwytujemy wynik, i dopiero później dokonujemy weryfikacji – nie definiujemy wcześniej naszych oczekiwań. Dla kontrastu w przypadku Mocka widać że definiujemy oczekiwania wcześniej.
Zestawienie
Zestawmy powyższe typy w ustandaryzowany sposób, aby zwizualizować różnice i podobieństwa.
Wstrzykiwanie danych wejściowych do SUT | Weryfikacja danych wyjściowych SUT | Dane wpływające na przebieg testu ustawiane bezpośrednio w teście | Użycie | |
---|---|---|---|---|
Dummy | Nie | Nie | Nie | Parametry metody lub właściwości |
Stub | Tak | Nie | Wejściowe | Przekazanie danych wejściowych do SUT |
Fake | Nie | Nie | Nie | Zmniejszanie zależności |
Mock | Opcjonalnie | Weryfikuje względem oczekiwań | Wyjściowe i opcjonalnie wejściowe | Weryfikacja wyjścia SUT |
Spy | Opcjonalnie | Przechwytuje w celu dalszej weryfikacji | Opcjonalnie wejściowe | Weryfikacja wyjścia SUT |
A Ty z jakich typów Double korzystasz? Podziel się swoimi doświadczeniami w komentarzach poniżej!