При тестировании часто встречается паттерн: одна и та же функция вызывается с разными параметрами, и для каждого набора входных данных проверяется результат. Такие тесты выглядят очень похожими и вызывают желание сократить дублирование.
Рассмотрим простой пример. У нас есть функция 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».
Зачем нужны провайдеры данных?
- Снижение дублирования кода. Один тест — множество проверок.
- Читаемость вывода. При ошибке PHPUnit покажет, с какими данными тест упал.
- Независимость запусков. Каждый набор данных — отдельный запуск, поэтому падение одного не влияет на остальные.
- Удобство отладки. Легче понять, какой именно вход привёл к ошибке.
Именованные наборы данных
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 — мощный и удобный инструмент, который делает тесты компактнее, понятнее и информативнее. Его стоит использовать во всех случаях, где одна и та же логика проверяется на разных входных данных.
Самостоятельная работа
Протестируйте с помощью Data Provider функцию calculateRectangleArea(int $length, int $width): int
:
function calculateRectangleArea(int $length, int $width): int
{
if ($length <= 0 || $width <= 0) {
return null; // Invalid dimensions
}
return $length * $width;
}
Функция calculateRectangleArea()
принимает два параметра: длину и ширину прямоугольника. Она проверяет, что оба параметра положительные, и возвращает площадь прямоугольника, вычисленную как произведение длины и ширины. Если один из параметров не является положительным числом, функция возвращает null, указывая на недопустимые размеры.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.