Статьи

Не уточняйте свои издевательства

Проверка на основе поведения противоположна проверке на основе состояния в том смысле, что ее утверждение не выполняется для значений и объектов, возвращаемых тестируемой системой. Вместо этого они делаются на сообщениях, которые отправляет SUT — в объектно-ориентированной парадигме Java / PHP, на вызовах, выполняемых с другими объектами.

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

Таким образом, в духе «Не повторяйся» и не пиши больше кода, чем необходимо, мы можем максимально сократить наши ложные ожидания . Эта статья поможет вам сделать это с помощью синтаксиса PHPUnit, но большинство концепций применимо ко всем фреймворкам xUnit.

Что не является ложным ожиданием

Даже если PHPUnit генерирует все виды тестовых дубликатов с помощью getMock (), и все библиотеки для тестовых дубликатов называются средами моделирования, не вся конфигурация, которую вы можете указать, является частью Mock в первоначальном смысле: проверка на основе поведения.

Например, возвращаемые типы и значения не представляют интереса для Mock; они будут в заглушке, но Mocks сосредоточены на проверке сообщений, которые тестируемый объект отправляет своим сотрудникам, а не данных, которые передаются в противоположном смысле.

Помимо не указания возвращаемых значений для упрощения интерфейсов, вы не хотите записывать слишком строгие ожидания, указывая все точные вызовы для всех методов.

Например, сегодня я был озадачен неудачей теста, который содержал этот код:

$mock->expects($this->at(2))
     ->method('doSomething')
     ->with('argument')
     ->will($this->returnValue('result'));
$mock->expects($this->at(3))
     ->method('doSomething')
     ->with('anotherArgument')
     ->will($this->returnValue('anotherResult'));

что для неинициированного означает, что метод doSomething () будет выполнять эти проверки и возвращать указанные значения только при 3-м и 4-м вызовах (и ноль в других случаях).

Вы можете себе представить, что незначительное изменение кода реализации почти всегда будет нарушать этот тест. Это (по общему признанию крайний) пример того, как слепая спецификация фиксированного списка вызовов методов приводит к большему обслуживанию : случай, когда делается слишком много предположений об интерфейсе.

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

(На самом деле они могут, но только с сопоставлениями at (), которые в любом случае являются чрезмерным ограничением по отношению к именам методов: это все равно, что заменить числовой массив на ассоциативный.)

Количество

Количество может быть указано с помощью:

  • $ this-> never () (0 звонков)
  • $ this-> Once () (1 звонок)
  • $ this-> точно ($ раз) ($ раз звонит)

Однако $ this-> any () разрешает неограниченное количество вызовов, что особенно полезно, например, при указании обратных вызовов. Я всегда советую использовать $ this-> any () вместо $ this-> Once () для всех методов запроса (чтение состояния объекта), так как не имеет значения, выполняется ли операция чтения несколько раз. В этом случае вы действительно строите заглушку, если только вы не тестируете кеш или очередь.

Однако обратите внимание, что если вы не вызываете метод с ожидаемым значением $ this-> any (), ошибка не выдается. Убедитесь, что это то, что вы ожидаете. ?

аргументы

$ this-> with () принимает столько аргументов, сколько ожидать от вызова метода. Однако, если вы укажете скалярные переменные, проверка будет строгой:

$expectation->with(true, 42);

(в этих примерах я имею в виду $ Ожидаемое значение, возвращаемое значением $ mock-> ожидаемого (), которое больше не является имитатором, а объектом ожидания.)

Но можно использовать и другие ограничения, особенно если мы не знаем фактическое значение, которое будет передано, или если оно меняется очень часто (дата, случайное число …):

$expectation->with($this->instanceOf('Iterator')) 

Вы также можете игнорировать некоторые аргументы, сопоставляя только интересные:

$expectation->with(42, $this->anything());

Программные ожидания

Когда нет связанного средства сопоставления, подходящего для того, что вы хотите проверить по аргументу, вы всегда можете прибегнуть к некоторому пользовательскому коду PHP:

$self = $this;
$expectation->will($this->returnCallback(function($argument) use ($self) {
    $self->assertTrue(/* ... make your checks here */);
});

В PHP 5.4 вы можете использовать $ this непосредственно вместо $ self.

Обратите внимание, что если вы хотите использовать это ожидание повторно, вы можете создать подкласс PHPUnit_Framework_Constraint:

class Number extends PHPUnit_Framework_Constraint
{
    public static function even()
    {
        return new self();
    }

    protected function matches($other)
    {
        return $other % 2 == 0;
    }

    public function toString()
    {
        return 'is even';
    }

    /*
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     */
    protected function failureDescription($other)
    {
        return "is even";
    }
}

и создайте это так:

$expectation->with(Number::even());

Выводы

Не придерживайтесь базового $ mock-> Ожидает ($ this-> Once ()) -> Метод (‘name’) -> с (42) -> Будет ($ this-> ReturnValue (100)) шаблон. PHPUnit (и большинство других инструментов для мошенничества) более гибок, чем вы думаете, и тесты можно усовершенствовать, чтобы урезать их, вместо того, чтобы проверять миллион бесполезных деталей.