Функции, которые бросают исключения
Многие функции PHP отклоняют некорректный ввод, бросая исключение: при успехе они возвращают результат, при ошибке бросают исключение и вообще ничего не возвращают.
Тест для такой функции должен проверять оба пути: корректный ввод возвращает правильное значение без исключения, а некорректный ввод действительно приводит к исключению — функция, которая молча принимает плохой ввод, тоже ошибка.
Успешный путь
Для корректного ввода тест просто вызывает функцию и сравнивает значение. Дополнительная проверка ошибки не нужна: если вызов неожиданно бросит исключение, PHPUnit сам сообщит об исключении и провалит тест.
Корректный ввод, без исключения
safeDivide(10, 2) должна вернуть 5.0 и ничего не бросить. Достаточно одного assertSame — неожидаемое исключение провалит тест без всякого дополнительного кода.
Вывод: PHP-скрипт
<?php
use PHPUnit\Framework\TestCase;
function safeDivide(float $a, float $b): float {
if ($b == 0) {
throw new InvalidArgumentException("нельзя делить на ноль");
}
return $a / $b;
}
final class SafeDivideTest extends TestCase {
public function testSafeDivideGood(): void {
$this->assertSame(5.0, safeDivide(10, 2));
}
}
Ошибочный путь
Вторая половина контракта: некорректный ввод должен приводить к исключению. Тест объявляет ожидание до вызова с помощью expectException, а затем вызывает функцию.
Если вызов бросает объявленное исключение, ожидание считается пройденной проверкой. Если вместо этого вызов завершается нормально, тест проваливается — функция приняла ввод, который должна была отклонить.
Некорректный ввод должен бросить исключение
Деление на ноль обязано завершиться ошибкой. Тест ожидает InvalidArgumentException; safeDivide(10, 0) бросает его, поэтому запуск сообщает OK (1 test, 1 assertion). Если бы safeDivide здесь вернула значение, тест провалился бы.
Вывод: PHP-скрипт
<?php
use PHPUnit\Framework\TestCase;
function safeDivide(float $a, float $b): float {
if ($b == 0) {
throw new InvalidArgumentException("нельзя делить на ноль");
}
return $a / $b;
}
final class SafeDivideTest extends TestCase {
public function testSafeDivideByZero(): void {
$this->expectException(InvalidArgumentException::class);
safeDivide(10, 0);
}
}
Таблица с полем throws
Оба пути укладываются в один data-провайдер: добавьте каждой строке поле throws, которое говорит, ожидается ли исключение.
Тестовый метод сначала проверяет это поле — для строки с исключением он объявляет expectException, вызывает функцию и выходит. Для успешных строк он сравнивает значение как обычно.
Успешные и ошибочные случаи в одной таблице
Четыре строки: три обычных деления и одно деление на ноль, где throws равно true. Один тестовый метод покрывает весь контракт функции — при запуске с --testdox все четыре случая проходят.
Вывод: PHP-скрипт
<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
function safeDivide(float $a, float $b): float {
if ($b == 0) {
throw new InvalidArgumentException("нельзя делить на ноль");
}
return $a / $b;
}
final class SafeDivideTest extends TestCase {
public static function divideCases(): array {
return [
"simple division" => [10, 2, 5.0, false],
"negative result" => [-9, 3, -3.0, false],
"divide zero" => [0, 5, 0.0, false],
"divide by zero" => [10, 0, 0.0, true],
];
}
#[DataProvider('divideCases')]
public function testSafeDivide(float $a, float $b, float $want, bool $throws): void {
if ($throws) {
$this->expectException(InvalidArgumentException::class);
safeDivide($a, $b);
return;
}
$this->assertSame($want, safeDivide($a, $b));
}
}