Как писать unit-тесты с PHPUnit?php-58

Unit-тестирование — это практика проверки отдельных изолированных частей кода (юнитов) на корректность работы. PHPUnit — стандартный инструмент для этого в PHP-экосистеме.

Базовые принципы

  1. Изоляция: Тесты не должны зависеть друг от друга
  2. Детерминированность: Результат должен быть одинаковым при одинаковых входных данных
  3. Простота: Один тест — одна проверка
  4. Скорость: Тесты должны выполняться быстро

Структура теста

Базовый класс теста наследуется от PHPUnit\Framework\TestCase:

use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase
{
    // Тестовые методы начинаются с test*
    public function testAddTwoNumbers(): void
    {
        $calculator = new Calculator();
        $result = $calculator->add(2, 3);

        $this->assertEquals(5, $result);
    }
}

Основные assert-методы

Метод Проверка
assertEquals() Равенство значений
assertTrue() Истинность значения
assertCount() Количество элементов массива
assertInstanceOf() Принадлежность к классу
assertStringContainsString() Наличие подстроки

Тестирование исключений

public function testDivisionByZero(): void
{
    $this->expectException(DivisionByZeroError::class);

    $calculator = new Calculator();
    $calculator->divide(10, 0);
}

Фикстуры

Для подготовки и очистки окружения:

protected function setUp(): void
{
    $this->calculator = new Calculator();
    $this->logger = new TestLogger(); // Заглушка
}

protected function tearDown(): void
{
    unset($this->calculator);
}

Моки и стабы

Использование TestCase::createMock() для замены зависимостей:

public function testPaymentProcessing(): void
{
    $paymentGateway = $this->createMock(PaymentGateway::class);
    $paymentGateway->method('process')
                  ->willReturn(true);

    $processor = new PaymentProcessor($paymentGateway);
    $this->assertTrue($processor->makePayment(100));
}

Data Providers

Для тестирования с разными наборами данных:

/**
 * @dataProvider additionProvider
 */
public function testAdd(int $a, int $b, int $expected): void
{
    $this->assertEquals($expected, $this->calculator->add($a, $b));
}

public function additionProvider(): array
{
    return [
        [1, 1, 2],
        [0, 5, 5],
        [-1, 1, 0],
    ];
}

Тестирование приватных методов

Через рефлексию (использовать осторожно!):

public function testPrivateMethod(): void
{
    $class = new MyClass();
    $reflector = new ReflectionClass($class);
    $method = $reflector->getMethod('privateMethod');
    $method->setAccessible(true);

    $result = $method->invokeArgs($class, ['param']);
    $this->assertEquals('expected', $result);
}

Best Practices

  1. Именование тестов: methodName_Scenario_ExpectedBehavior
  2. 3A-паттерн: Arrange-Act-Assert
  3. Покрытие кода: Стремитесь к 70-80% для бизнес-логики
  4. Избегайте зависимостей от БД/сети в unit-тестах

Конфигурация

Пример phpunit.xml:

<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="unit">
            <directory>tests/Unit</directory>
        </testsuite>
    </testsuites>
    <coverage>
        <include>
            <directory>src</directory>
        </include>
    </coverage>
</phpunit>

Резюмируем:

PHPUnit предоставляет мощный инструментарий для создания изолированных, поддерживаемых и надежных unit-тестов. Грамотное тестирование значительно снижает количество багов в production и облегчает рефакторинг кода.