PHP: Автоматическое тестирование

Теория: Data Provider

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

Рассмотрим простой пример. У нас есть функция cube(int $x): int, которая возвращает куб числа. Вместо того чтобы писать несколько однотипных тестов, мы можем воспользоваться механизмом Data Provider, встроенным в PHPUnit.

Что такое Data Provider?

Data Provider — это специальный метод, который возвращает массив наборов данных. Каждый набор передаётся в тестовый метод в виде аргументов.

Пример теста с использованием Data Provider:

<?php

namespace Hexlet\Package\Tests;

// Важно импортировать DataProvider, чтобы мы могли его использовать
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

use function Hexlet\Package\Math\cube;

class MathTest extends TestCase
{
    // Связываем метод-провайдер с методом-тестом
    #[DataProvider('cubeProvider')]
    public function testCube(int $expected, int $argument): void
    {
        $this->assertEquals($expected, cube($argument));
    }

    // cubeProvider() - это метод-провайдер
    public static function cubeProvider(): array
    {
        return [
            [1, 1],
            [8, 2],
            [27, 3],
            [64, 4],
        ];
    }
}

В этом примере:

  • Метод testCube() получает два аргумента: ожидаемое значение $expected и входной параметр $argument.
  • Метод cubeProvider() возвращает массив массивов — каждый вложенный массив содержит параметры для одного запуска теста.
  • PHPUnit сам подставит пары значений в тестовый метод.

Как работают методы провайдеры

Как метод testCube() узнает про метод-провайдер? За счет вызова атрибута #[DataProvider('cubeProvider')]. Атрибуты в PHP — это специальные метки, которые пишутся в квадратных скобках #[...] над функциями, классами, переменными. Они не выполняются сами по себе, а просто сообщают PHP (или другим инструментам), что с этим кодом делать.

<?php

// ...
#[DataProvider('cubeProvider')]
public function testCube(int $expected, int $argument): void
// ...

Здесь #[DataProvider('cubeProvider')] — это атрибут. Он говорит PHPUnit: «Эта функция будет запускаться несколько раз — с разными значениями, которые вернёт cubeProvider».

Зачем нужны провайдеры данных?

  1. Снижение дублирования кода. Один тест — множество проверок.
  2. Читаемость вывода. При ошибке PHPUnit покажет, с какими данными тест упал.
  3. Независимость запусков. Каждый набор данных — отдельный запуск, поэтому падение одного не влияет на остальные.
  4. Удобство отладки. Легче понять, какой именно вход привёл к ошибке.

Именованные наборы данных

PHPUnit позволяет дать имена наборам данных. Посмотрите на пример ниже:

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Hexlet\Package\User;

class UserTest extends TestCase
{
    #[DataProvider('userDataProvider')]
    public function testGetName(string $name, array $children): void
    {
        $user = new User($name, $children);

        $this->assertEquals($name, $user->getName());
        $this->assertEquals(collect($children), $user->getChildren());
    }

    public static function userDataProvider(): array
    {
        return [
            'one·child' => ['john', [new User('Mark')]],
            'no children' => ['anna', []],
            'two children' => ['alice', [new User('Tom'), new User('Eva')]],
        ];
    }
}

Если запустить этот тест с опцией --testdox, то мы получим вывод имени тестового метода и набора данных, с которым были запущены тесты

composer exec --verbose phpunit tests/UserTest.php -- --testdox
> __exec_command: phpunit 'tests/UserTest.php' '--testdox'
PHPUnit 12.1.4 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.0
Configuration: /hexlet-php-testing/phpunit.xml

...                                                                 3 / 3 (100%)

Time: 00:00.014, Memory: 10.00 MB

User (Hexlet\Package\Tests\User)
 ✔ Get name with one·child
 ✔ Get name with no·children
 ✔ Get name with two

OK (3 tests, 6 assertions)

Что будет при ошибке?

Если сделать ошибку в логике функции, например, возвращать квадрат вместо куба, то:

  • Обычный тест без провайдера просто покажет: ожидалось 8, получено 4. Но не скажет, какой был вход.
  • Тест с провайдером покажет: ожидалось 8, получено 4 при аргументе 2. Это сильно упрощает отладку.
....F

Time: 00:00.025, Memory: 10.00 MB

There was 1 failure:

1) Hexlet\Package\Tests\MathTest::testCube with data set #4 (65, 4)
Failed asserting that 64 matches expected 65.

/projects/hexlet-php-testing/tests/MathTest.php:15

Выводы

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

Рекомендуемые программы