Test Doubles
Вступление
Концепция Test Doubles была представлена Gerard Meszaros в Месаросе в 2007 году с цитатой.
« Иногда бывает просто сложно протестировать тестируемую систему (SUT), потому что это зависит от других компонентов, которые нельзя использовать в тестовой среде. Это может быть связано с тем, что они недоступны, они не будут возвращать результаты, необходимые для теста, или потому, что их выполнение будет иметь нежелательные побочные эффекты. В других случаях наша стратегия тестирования требует, чтобы мы имели больший контроль или видимость внутреннего поведения SUT.
Когда мы пишем тест, в котором мы не можем (или решили не использовать) реальный зависимый компонент (DOC), мы можем заменить его на Test Double. Test Double не должен вести себя точно так же, как настоящий DOC; он просто должен предоставить тот же API, что и реальный, чтобы SUT считал его настоящим! »
Методы `createMock ($ type)` и `getMockBuilder ($ type)`, предоставляемые PHPUnit, можно использовать в тесте для автоматической генерации объекта, который может выполнять функцию двойного теста для указанного исходного типа (интерфейса или имени класса). Этот тестовый двойной объект можно использовать в любом контексте, где ожидается или требуется объект оригинального типа.
Метод `createMock ($ type)` немедленно возвращает двойной объект теста для указанного типа (интерфейса или класса). Создание этого двойного теста выполняется с использованием лучших практик по умолчанию. Методы `__construct ()` и `__clone ()` исходного класса не выполняются, а аргументы, передаваемые методу test double, не будут клонированы. Если эти значения по умолчанию не то, что вам нужно, вы можете использовать метод `getMockBuilder ($ type)`, чтобы настроить двойную генерацию теста, используя свободный интерфейс.
По умолчанию все методы исходного класса заменяются фиктивной реализацией, которая просто возвращает значение null (без вызова исходного метода). Например, используя метод `will ($ this-> returnValue ())`, вы можете настроить эти фиктивные реализации на возврат значения при вызове.
Ограничение: финальный, приватный и статический методы.
Обратите внимание, что финальные, приватные и статические методы не могут быть заглушки или насмешки. Они игнорируются двойной функциональностью теста PHPUnit и сохраняют свое первоначальное поведение, за исключением статических методов, которые будут заменены методом, вызывающим исключение `/ PHPUnit / Framework / MockObject / BadMethodCallException`.
Столбики
Практика замены объекта тестовым двойником, который (опционально) возвращает сконфигурированные возвращаемые значения, называется заглушкой . Вы можете использовать заглушку для «замены реального компонента, от которого зависит SUT, чтобы в тесте была контрольная точка для косвенных входов SUT. Это позволяет тесту форсировать пути SUT, которые он не мог бы выполнить ».
Пример 8.2 показывает, как заглушить вызовы метода и настроить возвращаемые значения. Сначала мы используем метод `createMock ()`, предоставляемый классом `PHPUnit / Framework / TestCase`, чтобы настроить объект-заглушку, который выглядит как объект SomeClass (пример 8.1). Затем мы используем интерфейс Fluent, который предоставляет PHPUnit, чтобы указать поведение заглушки. По сути, это означает, что вам не нужно создавать несколько временных объектов и впоследствии соединять их вместе. Вместо этого вы цепляете вызовы методов, как показано в примере. Это приводит к более читаемому и «свободному» коду.
<?php
class SomeClass
{
public function doSomething()
{
// Do something.
}
}
?>
Пример 8.2. Заглушка вызова метода для возврата фиксированного значения
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testStub()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->willReturn('foo');
// Calling $stub->doSomething() will now return
// 'foo'.
$this->assertSame('foo', $stub->doSomething());
}
}
?>
Ограничение: Методы с именем «метод»
Приведенный выше пример работает только тогда, когда исходный класс не объявляет метод с именем «method».
Если исходный класс объявляет метод с именем «method», то $ stub-> Ожидает ($ this-> any ()) -> method ('doSomething') -> willReturn ('foo'); должен быть использован.
«За кулисами» PHPUnit автоматически генерирует новый класс PHP, который реализует желаемое поведение при использовании метода createMock ().
В примере 8.3 показан пример использования свободного интерфейса Mock Builder для настройки создания двойного теста. Конфигурация этого двойного теста использует те же самые лучшие практические значения по умолчанию, которые использует createMock ().
Пример 8.3 Использование API Mock Builder можно использовать для настройки сгенерированного двойного класса теста
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMockBuilder(SomeClass::class)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->getMock();
// Configure the stub.
$stub->method('doSomething')
->willReturn('foo');
// Calling $stub->doSomething() will now return
// 'foo'.
$this->assertSame('foo', $stub->doSomething());
}
}
До сих пор в примерах мы возвращали простые значения, используя `willReturn ($ value)`. Этот короткий синтаксис такой же, как `will ($ this-> returnValue ($ value))`. Мы можем использовать вариации этого более длинного синтаксиса для достижения более сложного поведения заглушки.
Иногда вы хотите вернуть один из аргументов вызова метода (без изменений) как результат вызова метода с заглушкой. В примере 8.4 показано, как этого добиться, используя returnArgument () вместо returnValue ().
Пример 8.4. Заглушка вызова метода для возврата одного из аргументов
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testReturnArgumentStub()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->will($this->returnArgument(0));
// $stub->doSomething('foo') returns 'foo'
$this->assertSame('foo', $stub->doSomething('foo'));
// $stub->doSomething('bar') returns 'bar'
$this->assertSame('bar', $stub->doSomething('bar'));
}
}
?>
При тестировании свободного интерфейса иногда полезно, чтобы метод-заглушка возвращал ссылку на объект-заглушку. Пример 8.5 показывает, как вы можете использовать returnSelf () для достижения этой цели.
Пример 8.5. Заглушка вызова метода для возврата ссылки на объект-заглушку
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testReturnSelf()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->will($this->returnSelf());
// $stub->doSomething() returns $stub
$this->assertSame($stub, $stub->doSomething());
}
}
?>
Иногда метод-заглушка должен возвращать разные значения в зависимости от предварительно определенного списка аргументов. Вы можете использовать returnValueMap () для создания карты, которая связывает аргументы с соответствующими возвращаемыми значениями. См. Пример 8.6 для примера.
Пример 8.6. Заглушка вызова метода для возврата значения из карты
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testReturnValueMapStub()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Create a map of arguments to return values.
$map = [
['a', 'b', 'c', 'd'],
['e', 'f', 'g', 'h']
];
// Configure the stub.
$stub->method('doSomething')
->will($this->returnValueMap($map));
// $stub->doSomething() returns different values depending on
// the provided arguments.
$this->assertSame('d', $stub->doSomething('a', 'b', 'c'));
$this->assertSame('h', $stub->doSomething('e', 'f', 'g'));
}
}
?>
Когда вызов метода-заглушки должен возвращать вычисленное значение вместо фиксированного (см. ReturnValue ()) или (неизмененный) аргумент (см. ReturnArgument ()), вы можете использовать returnCallback (), чтобы метод-заглушка возвращал результат функция обратного вызова или метод. См. Пример 8.7 для примера.
Пример 8.7. Заглушка вызова метода для возврата значения из обратного вызова
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testReturnCallbackStub()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->will($this->returnCallback('str_rot13'));
// $stub->doSomething($argument) returns str_rot13($argument)
$this->assertSame('fbzrguvat', $stub->doSomething('something'));
}
}
?>
Более простой альтернативой настройке метода обратного вызова может быть указание списка желаемых возвращаемых значений. Вы можете сделать это с помощью метода onConsecutiveCalls (). См. Пример 8.8 для примера.
Пример 8.8. Заглушка вызова метода для возврата списка значений в указанном порядке
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testOnConsecutiveCallsStub()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->will($this->onConsecutiveCalls(2, 3, 5, 7));
// $stub->doSomething() returns a different value each time
$this->assertSame(2, $stub->doSomething());
$this->assertSame(3, $stub->doSomething());
$this->assertSame(5, $stub->doSomething());
}
}
?>
Вместо того, чтобы возвращать значение, метод-заглушка также может вызвать исключение. В примере 8.9 показано, как использовать для этого `throwException ()`.
Пример 8.9. Заглушка вызова метода для создания исключения
<?php
use PHPUnit\Framework\TestCase;
class StubTest extends TestCase
{
public function testThrowExceptionStub()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->will($this->throwException(new Exception));
// $stub->doSomething() throws Exception
$stub->doSomething();
}
}
?>
Кроме того, вы можете написать заглушку самостоятельно и улучшить свой дизайн. Доступ к широко используемым ресурсам осуществляется через единый фасад, поэтому вы легко можете заменить ресурс заглушкой. Например, вместо прямых вызовов базы данных, разбросанных по всему коду, у вас есть один объект `Database`, разработчик интерфейса` IDatabase`. Затем вы можете создать заглушку реализации `IDatabase` и использовать ее для своих тестов. Вы даже можете создать опцию для запуска тестов с базой данных-заглушкой или реальной базой данных, чтобы вы могли использовать свои тесты как для локального тестирования во время разработки, так и для тестирования интеграции с реальной базой данных.
Функциональность, которая должна быть заглушена, имеет тенденцию объединяться в одном объекте, улучшая сплоченность. Предоставляя функциональность с помощью единого связного интерфейса, вы уменьшаете связь с остальной частью системы.
Предыдущий: Неполные и пропущенные тесты
Далее: нежное введение в модульное тестирование и тестирование
Новый контент: Composer: менеджер зависимостей для PHP , R программирования