Blog

05 grudnia 2014 Adam Puza

Double – konstrukcje naśladujące realne obiekty

Tagi:

Double – konstrukcje naśladujące realne obiekty

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!

Zobacz na blogu

09.09.2022
Marcin Jahn
It’s Not Just HTTP It’s Not Just HTTP

In today’s world of cloud-based solutions, distributed systems, and microservices-based architectures, network communication is a...

23.08.2022
Adam Mrowiec
Konferencja IPC 2022 Berlin Konferencja IPC 2022 Berlin

Pandemia wreszcie się kończy, dlatego w tym roku postanowiliśmy wrócić do naszych wyjazdów na konferencje....