Тестирование разработки на PHP

Раздел: Разработка на PHP -> Тестирование

Основы модульного тестирования с PHPUnit

Эффективным решением для тестирования PHP кода является использование PHPUnit. Это стандарт де факто для модульных тестов в экосистеме PHP. Установка выполняется через Composer:

composer require --dev phpunit/phpunit

Пример простого теста для класса Calculator:

use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase
{
    public function testAddition()
    {
        $calc = new Calculator();
        $result = $calc->add(2, 3);
        $this->assertEquals(5, $result);
    }
}

Запуск тестов осуществляется командой:

vendor/bin/phpunit tests/CalculatorTest.php

PHPUnit предоставляет множество утверждений (assertEquals, assertTrue, assertNull и т.д.), а также возможности для организации тестов в наборы, использования провайдеров данных и тегирования.

Альтернативные подходы включают:

  • PHPT - простые файлы сценариев для тестирования расширений PHP, часто используются для проверки поведения функций.
  • SimpleTest - более легковесный фреймворк, хорош для небольших проектов, но менее популярен.
  • Behat - инструмент поведенчески-ориентированного тестирования (BDD), позволяет писать тесты на естественном языке с поддержкой контекстов.
  • Codeception - надстройка над PHPUnit, добавляющая модули для тестирования БД, веб-интерфейсов и API, удобна для acceptance-тестов.
  • Mockery / Prophecy - библиотеки для создания заглушек и подмены объектов, часто используются совместно с PHPUnit.

Типичные проблемы и их решения:

  • Класс не найден - ошибка автозагрузки. Проверьте composer.json на наличие автозагрузки PSR-4 и выполните composer dump-autoload.
  • Тестирование protected/private методов - используйте рефлексию или выносите логику в отдельные публичные методы. Пример через ReflectionMethod:
  • $method = new ReflectionMethod('ClassName', 'methodName');
    $method->setAccessible(true);
    $result = $method->invoke($object, $args);
  • Медленные тесты - группируйте интеграционные и модульные тесты с помощью аннотаций @group, запускайте быстрые тесты отдельно.
  • База данных в тестах - используйте транзакции (Rollback на tearDown) или библиотеки типа Doctrine Fixtures. Пример с PHPUnit:
  • protected function setUp(): void
    {
        parent::setUp();
        $this->connection->beginTransaction();
    }
    
    protected function tearDown(): void
    {
        $this->connection->rollBack();
        parent::tearDown();
    }
  • Тестирование зависимостей от времени - используйте Carbon::setTestNow() или свои обёртки для изоляции.

Расширенные примеры тестирования

Тестирование с mock-объектами

Использование Mockery для имитации внешнего сервиса:

Пример
use Mockery as m;

class MailerTest extends TestCase
{
    public function testSendWelcomeEmail()
    {
        $mailer = m::mock('Mailer');
        $mailer->shouldReceive('send')
               ->once()
               ->with('user@example.com', 'Welcome')
               ->andReturn(true);

        $service = new UserRegistration($mailer);
        $result = $service->register('user@example.com');
        $this->assertTrue($result);
    }

    protected function tearDown(): void
    {
        m::close();
    }
}
OK (1 test, 1 assertion)

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

Проверка, что метод выбрасывает исключение при некорректных данных:

Пример
public function testDivisionByZero()
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage('Division by zero is not allowed');

    $calc = new Calculator();
    $calc->divide(10, 0);
}
OK (1 test, 2 assertions)

Провайдеры данных

Многократное выполнение теста с разными наборами параметров:

Пример
/**
 * @dataProvider additionProvider
 */
public function testAdd($a, $b, $expected)
{
    $calc = new Calculator();
    $this->assertEquals($expected, $calc->add($a, $b));
}

public function additionProvider(): array
{
    return [
        [0, 0, 0],
        [1, 2, 3],
        [-1, 1, 0],
        [2.5, 3.7, 6.2],
    ];
}
OK (4 tests, 4 assertions)

Тестирование HTTP-запросов (Guzzle + PHPUnit)

Использование mock-клиента для эмуляции ответа API:

Пример
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

public function testFetchUsers()
{
    $mock = new MockHandler([
        new Response(200, [], json_encode(['users' => [['id' => 1]]])),
    ]);
    $handlerStack = HandlerStack::create($mock);
    $client = new Client(['handler' => $handlerStack]);

    $service = new UserService($client);
    $users = $service->getUsers();
    $this->assertCount(1, $users);
    $this->assertEquals(1, $users[0]->id);
}
OK (1 test, 2 assertions)

Функциональное тестирование Symfony с Panther

Panther позволяет выполнять тесты в реальном браузере:

Пример
use Symfony\Component\Panther\PantherTestCase;

class LoginTest extends PantherTestCase
{
    public function testLoginPage()
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', '/login');

        $form = $crawler->selectButton('Login')->form([
            '_username' => 'admin',
            '_password' => 'admin123',
        ]);
        $client->submit($form);

        $this->assertPageContainsText('Dashboard');
    }
}
OK (1 test, 1 assertion)

Тестирование кэша с мокированием времени

Использование Carbon для фиксации времени:

Пример
use Carbon\Carbon;

public function testCacheExpiration()
{
    Carbon::setTestNow(Carbon::create(2023, 1, 1, 12, 0, 0));
    $cache = new CacheService();
    $cache->put('key', 'value', 10); // ttl 10 minutes

    $this->assertEquals('value', $cache->get('key'));

    Carbon::setTestNow(Carbon::create(2023, 1, 1, 12, 11, 0)); // 11 minutes later
    $this->assertNull($cache->get('key'));

    Carbon::setTestNow(); // reset
}
OK (1 test, 2 assertions)

Измерение покрытия кода

Генерация отчёта о покрытии с помощью Xdebug и PHPUnit:

Пример
phpunit --coverage-html coverage

Результат - HTML-папка coverage с детализацией по строками, методам и классам.

Тестирование PHP - comments

En
Php testing (php)