PHP
September 9, 2023

Сначала тесты, потом код - пошагово

Test-driven development (TDD) - это итеративный процесс разработки программного обеспечения, в котором сначала пишутся тесты, а затем пишется код, который проходит эти тесты.

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

Шаг 1:

Установка фреймворка для тестов к себе в проект.

composer require --dev phpunit/phpunit

Подробно о создании проекта через composer здесь.

Шаг 2:

Определите цель или функцию, которую вы хотели бы реализовать. Пусть это будет функция “greet”, которая принимает имя пользователя и возвращает приветствие.

Любой фреймворк, например CodeIgniter, модель Greeter.php

<?php declare(strict_types=1);
final class Greeter
{
    public function greet(string $name): string
    {
        return 'Hello, ' . $name . '!';
    }
}

Первым делом, надо понять что конкретно хотим тестировать. В данном случае, мы ожидаем, что метод “greet” возвращает строковое значение. При этом, переменная $name должна быть проверена.

К примеру, если вызвать метод “greet” с параметром “John”:

$greeting = $greeter->greet('John');

То ожидаем, что функция вернет тоже самое имя “John”.

Каким образом это сделать?

В Unut тестах есть класс TestCase в которм содержится множество методов для проверок. Для стравнения переменной с ожидаемым результатом подойдет метод "assertSame".

В тесте можно использовать различные методы утверждения (`assert*`), предоставляемые фреймворком PHPUnit. Кроме `assertSame()`, есть и другие методы, которые можно использовать в зависимости от того, что вы хотите проверить. Некоторые из таких методов включают:

  • `assertEquals()`: Утверждает, что два значения равны, сравнивая их безопасно по типу.
  • `assertTrue()`: Утверждает, что значение является истинным (имеет значение `true`).
  • `assertFalse()`: Утверждает, что значение является ложным (имеет значение `false`).
  • `assertNull()`: Утверждает, что значение равно `null`.
  • `assertNotNull()`: Утверждает, что значение не равно `null`.
  • `assertEmpty()`: Утверждает, что значение пусто (пустая строка, массив без элементов, `null`, `false` и т.д.).
  • `assertNotEmpty()`: Утверждает, что значение не пустое (содержит хотя бы один элемент, непустую строку и т.д.).

Это всего лишь несколько примеров методов утверждения, доступных в PHPUnit. Вы можете ознакомиться с полным списком методов и их возможностями в документации PHPUnit по адресу https://phpunit.de/documentation.html.

Шаг 3:

Напишите тест, который проверяет, что функция “greet” возвращает корректное приветствие для заданного имени. Например, вы можете написать тест, который проверяет, что “greet(‘John’)” возвращает "Hello, John!".

<?php

namespace unit;

use App\Models\Greeter;
use PHPUnit\Framework\TestCase;

final class GreeterTest extends TestCase{
    private $greeter;
    protected function setUp(): void
    {
        $this->greeter = new Greeter();
    }
    public function testGreet()
    {
        $name = 'Fff';
        $expected = 'Hello, John Doe!';
        $actual = $this->greeter->greet($name);
        $this->assertSame($expected, $actual);
    }
}

Здесь я заведомо указал в переменную $name не верное значение чтобы провалить тест. Метод "assertSame" сравнит $expected и $actual на идентичность (то есть проверяет, что значения равны и имеют тот же тип данных). Если значения не совпадают или имеют разный тип данных, то тест провалится и будет выведено соответствующее сообщение об ошибке.

Шаг 4:

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

php vendor/bin/phpunit tests/
PHPUnit 9.6.11 by Sebastian Bergmann and contributors.

...F..                                                              6 / 6 (100%)

Time: 00:00.072, Memory: 14.00 MB

There was 1 failure:

1) unit\GreeterTest::testGreetsWithName
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'Hello, John Doe!'
+'Hello, Fff!'

/var/www/html/web/tests/unit/GreeterTest.php:16

FAILURES!
Tests: 6, Assertions: 7, Failures: 1.

-'Hello, John Doe!' эта страка говорит о том, какой ответ ожидался;

+'Hello, Fff!' эта страка говорит о том, какой ответ пришел.

Вернемся в тест и введем в перменную $name правильное значение John Doe и тест пройдет проверку.

Шаг 5:

Напишите код, который пройдет этот тест.

<?php

namespace unit;

use App\Models\Greeter;
use PHPUnit\Framework\TestCase;

final class GreeterTest extends TestCase{
    private $greeter;
    protected function setUp(): void
    {
        $this->greeter = new Greeter();
    }
    public function testGreet()
    {
        $name = 'John Doe';
        $expected = 'Hello, John Doe!';
        $actual = $this->greeter->greet($name);
        $this->assertSame($expected, $actual);
    }
}

Шаг 6:

Запустите тестовый фреймворк еще раз, и теперь тест должен пройти.

php vendor/bin/phpunit tests/
PHPUnit 9.6.11 by Sebastian Bergmann and contributors.

Warning:       XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set

......                                                              6 / 6 (100%)

Time: 00:00.067, Memory: 12.00 MB

OK (6 tests, 7 assertions)

Заключение

Данный код является простым и предсказуемым, и его вполне можно считать достаточно надежным без проведения автоматического тестирования (unit testing). Однако, даже для такого простого кода проведение автоматического тестирования имеет некоторые преимущества:

  1. Защита от случайного внесения ошибок: Даже простые коды могут иметь опечатки и ошибки, которые могут быть не замечены без тестирования. Автоматическое тестирование помогает обнаружить такие ошибки и предотвратить их попадание в рабочую среду.
  2. Верификация ожидаемого поведения: Наличие тестов обеспечивает возможность проверить, что код работает, как ожидается, и что он возвращает правильные результаты в соответствии с требованиями к функциональности.
  3. Улучшение поддерживаемости: Наличие тестов делает код более легким для понимания и поддержки. При внесении изменений в код тесты помогают убедиться, что все основные функциональности остаются работоспособными.
  4. Документация: Тесты могут служить в качестве спецификации для поведения кода и документацией к нему. Они описывают ожидаемые результаты работы кода и помогают другим разработчикам понять, как он должен использоваться.

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