Dummy, Stub, и Mock 19.04.2016

При тестировании мы частно прибегаем к замене внешних зависимостей трестируемого кода тестовыми объектами. Назначение этих объектов может варьироваться от теста к тесту. Реализация одних тестовых кейсов требует дополнительного контроля по отношению к тестовому объекту, другие ожидают от них какого то стандартного поведения. Но несмотря ни на что, интерфейсы тестового и реального объектов должны совпадать.

В зависимости от возможностей тестового объекта его можно отнести к одному из трёх типов — dummy, stub или mock.

Dummy — самый простой и базовый тестовый объект, реализующий интерфейс минимально совместимый с интерфейсом реального объекта. Код такого тестового объекта не содержит никакой логики вообще. Как правило dummy-объекты могут заменить реальные только в тех тестовых кейсах, в которых обращение к тестовому объекту не имеет значения или просто отсутствует. Например, мы тестируем модуль, с двумя зависимостями и рассматриваем его взаимодействие с первой. В таких кейсах в качестве второй зависимости может быть передан dummy-объект.

В качестве примера рассмотрим dummy простейшего объекта запроса, реализующего следующий интерфейс (здесь и далее будет использоваться синтаксис TypeScript):

interface IRequest {
	execute():JQueryPromise<void>;
}

тогда код dummy-запроса мог бы выглядеть так:

class RequestDummy implements IRequest {
	public execute():JQueryPromise<void> {
		return undefined;
	}
}

Очевидно, код трестируемого модуля не сможет использовать подобный объект запроса, однако dummy реализует минимальный интерфейс для того, чтобы компилятор не выдал ошибок несоответствия типов.

Stub — заглушка — тестовый объект, частично реализующий логику реального объекта на уровне «валидные данные на входе — валидные на выходе». Как правило, код заглушки содержит тривиальную логику, имитирующую работу нескольких методов реального объекта. Сам stub может быть получен наследованием соответствующего класса dummy и переопределением его методов.

Код, реализующий stub для примера, описанного выше, выглядит так:

class RequestStub implements IRequest {
	public execute():JQueryPromise<void> {
		return $.when();
	}
}

При использовании в качестве тестового объекта такой stub симулирует успешное выполнение запроса.

Mock — самый сложный тип тестового объекта. Наряду с логикой имитации поведения реального объекта так же содержит код, реализующий функционал контроля доступа к тем или иным методам, а также состоянию объекта в целом. По сравнению с реальным объектом mock может обладать расширенным интерфейсом, предназначенным для контроля его внутреннего состояния и получения статистики в процессе выполнения теста. Как правило, именно моковые объекты являются местом сосредоточения велосипедов и костылей в коде тестов, что негативно влияет на читаемость и понятность тестов.

Код, реализующий mock в нашем примере, может выглядеть, например так:

class RequestMock implements IRequest {
	public execute():JQueryPromise<void> {
		return this.deferred.promise();
	}

	public resolve():void {
		this.deferred.resolve();
	}

	public reject():void {
		this.deferred.reject();
	}

	private deferred:JQueryDeferred<void> = $.Deferred<void>();
}

Как видно из примера, интерфейс и функционал тестового объекта обогатился методами, позволяющими контролировать результат выполнения запроса.

Итак, мы рассмотрели классификацию тестовых объектов. В следующей статье на эту темы мы рассмотрим подход к созданию тестовых объектов с использованием библиотеки для описания тестов Jasmine.

by 19.04.2016