Провайдеры данных
Провайдеры данных позволяют запускать один тест с разными наборами входных данных. Каждый набор — отдельный запуск теста.
Плагин не требует настройки.
#[DataSet]
Объявляет набор аргументов для параметризованного теста. Можно использовать многократно — каждый атрибут создаёт отдельный запуск.
#[DataSet(array $arguments, ?string $name = null)]Параметры:
$arguments- Массив значений, передаваемых в тестовый метод.
$name- Метка для отображения в отчётах. Помогает понять, какой сценарий упал.
Примеры:
#[Test]
#[DataSet([1, 1, 2])]
#[DataSet([2, 3, 5])]
#[DataSet([0, 0, 0])]
public function testSum(int $a, int $b, int $expected): void
{
Assert::same($expected, $a + $b);
}С метками:
#[DataSet([1, 1, 2], 'positive numbers')]
#[DataSet([-1, -1, -2], 'negative numbers')]
#[DataSet([0, 0, 0], 'zeros')]
public function testSum(int $a, int $b, int $expected): void { ... }#[DataProvider]
Предоставляет данные для параметризованного теста из метода или вызываемого объекта.
#[DataProvider(callable|string $provider)]Параметры:
$provider- Источник данных: имя метода (
'method'), callable ([Class::class, 'method']), замыкание или вызываемый объект. Должен возвращатьiterable. Строковые ключи элементов становятся метками датасетов в отчётах.
Примеры:
#[Test]
#[DataProvider('userDataProvider')]
public function testUserValidation(string $email, bool $expected): void
{
$isValid = $this->validator->validate($email);
Assert::same($expected, $isValid);
}
public function userDataProvider(): iterable
{
yield ['valid@example.com', true];
yield ['invalid', false];
yield ['test@domain.co.uk', true];
}Гибкие источники провайдеров
#[DataProvider]#[DataProvider(callable|string $provider)]Предоставляет данные для параметризованного теста из метода или вызываемого объекта. принимает различные типы вызываемых объектов:
Имя метода из того же класса:
#[DataProvider('dataProvider')]
public function testSomething($data): void { ... }Метод из другого класса:
#[DataProvider([DataSets::class, 'userScenarios'])]
public function testUser($data): void { ... }Замыкание непосредственно в атрибуте (PHP 8.5+):
#[DataProvider(fn() => [
[1, 2, 3],
[5, 5, 10],
])]
public function testAddition(int $a, int $b, int $expected): void { ... }Вызываемый объект:
#[DataProvider(new UserDataProvider())]
public function testUser($data): void { ... }Вызываемые объекты особенно полезны для разделения логики загрузки данных. Например, загрузка тестов из JSON/CSV файлов в выделенный класс позволяет сохранить код тестов чистым.
Метки датасетов
Метки задаются через строковые ключи массива: в
public function userDataProvider(): array
{
return [
'valid email' => ['test@example.com', true],
'invalid format' => ['not-an-email', false],
'empty string' => ['', false],
];
}#[DataZip]
Объединяет провайдеры попарно по индексу.
#[DataZip(DataProviderAttribute ...$providers)]Первый элемент из первого провайдера соединяется с первым из второго, второй со вторым, и так далее. Аргументы из всех провайдеров объединяются в один вызов теста.
Параметры:
$providers- Провайдеры данных для попарного объединения.
Примеры:
#[DataZip(
new DataProvider('credentials'),
new DataProvider('expectedPermissions'),
)]
public function testUserPermissions(string $login, string $password, array $permissions): void
{
$user = $this->auth->login($login, $password);
Assert::same($permissions, $user->getPermissions());
}
// credentials: [['admin', 'secret'], ['guest', '1234']]
// expectedPermissions: [[['read', 'write', 'delete']], [['read']]]
//
// Тест запустится 2 раза:
// 1. admin/secret → ['read', 'write', 'delete']
// 2. guest/1234 → ['read']Провайдеры разной длины
Если провайдеры имеют разную длину, количество датасетов определяется самым коротким провайдером:
#[DataZip(
new DataProvider('inputs'), // 3 элемента
new DataProvider('outputs'), // 2 элемента
)]
public function testTransform(string $input, string $output): void { ... }
// inputs: [['a'], ['b'], ['c']]
// outputs: [['x'], ['y']]
//
// Запустится 2 раза (по длине outputs):
// 1. 'a', 'x'
// 2. 'b', 'y'
// Третий элемент inputs ('c') игнорируетсяКлючи в отчётах
Метки датасетов соединяются через |. Если датасеты называются admin и full-access, в отчёте будет admin|full-access.
#[DataCross]
Создаёт все возможные комбинации из провайдеров (декартово произведение).
#[DataCross(DataProviderAttribute ...$providers)]Параметры:
$providers- Провайдеры данных для комбинирования.
Примеры:
#[DataCross(
new DataProvider('browsers'),
new DataProvider('screenSizes'),
)]
public function testResponsiveLayout(string $browser, int $width, int $height): void
{
$this->driver->setBrowser($browser);
$this->driver->setViewport($width, $height);
Assert::true($this->page->isLayoutCorrect());
}
// browsers: [['chrome'], ['firefox'], ['safari']]
// screenSizes: [[1920, 1080], [768, 1024], [375, 667]]
//
// Запустится 9 раз — каждый браузер с каждым разрешением:
// chrome × 1920×1080, chrome × 768×1024, chrome × 375×667,
// firefox × 1920×1080, ...Следите за количеством комбинаций
Число тестов растёт мультипликативно. Три провайдера по 5 элементов — это уже 125 тестов. Используйте #[DataCross]#[DataCross(DataProviderAttribute ...$providers)]Создаёт все возможные комбинации из провайдеров (декартово произведение). осознанно.
Ключи в отчётах
Метки соединяются через ×. Датасеты chrome и mobile дадут ключ chrome×mobile.
#[DataUnion]
Объединяет данные из нескольких провайдеров в один последовательный набор.
#[DataUnion(DataProviderAttribute ...$providers)]Для объединения данных обычно достаточно перечислить несколько #[DataProvider]#[DataProvider(callable|string $provider)]Предоставляет данные для параметризованного теста из метода или вызываемого объекта. или #[DataSet]#[DataSet(array $arguments, ?string $name = null)]Объявляет набор аргументов для параметризованного теста. Можно использовать многократно — каждый атрибут создаёт отдельный запуск. над методом. #[DataUnion]#[DataUnion(DataProviderAttribute ...$providers)]Объединяет данные из нескольких провайдеров в один последовательный набор. нужен, когда объединение должно произойти внутри другого атрибута — например, внутри #[DataCross]#[DataCross(DataProviderAttribute ...$providers)]Создаёт все возможные комбинации из провайдеров (декартово произведение). или #[DataZip]#[DataZip(DataProviderAttribute ...$providers)]Объединяет провайдеры попарно по индексу..
Параметры:
$providers- Провайдеры данных для объединения в один набор.
Примеры:
#[DataCross(
new DataUnion(
new DataProvider('legacyFormats'),
new DataProvider('modernFormats'),
),
new DataProvider('compressionLevels'),
)]
public function testExport(string $format, int $compression): void
{
// Все форматы (legacy + modern) скрещиваются с каждым уровнем сжатия
}Комбинирование провайдеров
Внутри #[DataZip]#[DataZip(DataProviderAttribute ...$providers)]Объединяет провайдеры попарно по индексу., #[DataCross]#[DataCross(DataProviderAttribute ...$providers)]Создаёт все возможные комбинации из провайдеров (декартово произведение). и #[DataUnion]#[DataUnion(DataProviderAttribute ...$providers)]Объединяет данные из нескольких провайдеров в один последовательный набор. можно использовать любые провайдеры данных — #[DataProvider]#[DataProvider(callable|string $provider)]Предоставляет данные для параметризованного теста из метода или вызываемого объекта., #[DataSet]#[DataSet(array $arguments, ?string $name = null)]Объявляет набор аргументов для параметризованного теста. Можно использовать многократно — каждый атрибут создаёт отдельный запуск., а также вкладывать их друг в друга.
Смешивание типов
Удобно, когда часть параметров фиксирована, а часть приходит из провайдера:
#[DataCross(
new DataSet(['mysql'], 'mysql'),
new DataSet(['pgsql'], 'pgsql'),
new DataProvider('migrationScenarios'),
)]
public function testMigration(string $driver, array $scenario): void { ... }Или компактнее через #[DataProvider]#[DataProvider(callable|string $provider)]Предоставляет данные для параметризованного теста из метода или вызываемого объекта. для драйверов:
#[DataCross(
new DataProvider('databaseDrivers'),
new DataProvider('migrationScenarios'),
)]
public function testMigration(string $driver, array $scenario): void { ... }Вложенные комбинации
Для сложных сценариев провайдеры можно вкладывать:
#[DataZip(
new DataCross(
new DataProvider('users'),
new DataProvider('roles'),
),
new DataProvider('expectedResults'),
)]
public function testAccessControl(string $user, string $role, bool $expected): void
{
$this->actAs($user)->withRole($role);
Assert::same($expected, $this->canAccess('/admin'));
}
// users × roles даёт все комбинации пользователь-роль,
// затем они попарно соединяются с ожидаемыми результатами