Тестирование PHP приложений: от юнит до функциональных тестов

Раздел: Программирование на PHP -> Тестирование

Обзор систем тестирования PHP

Основное решение: PHPUnit – это де-факто стандарт для модульного тестирования в PHP. Он предоставляет гибкий набор утверждений, фикстуры, методы для организации тестов и интеграцию с CI/CD. Ниже рассмотрены базовые принципы работы с PHPUnit.

Для установки PHPUnit используйте Composer (требуется PHP 7.4+). Пример команды:

composer require --dev phpunit/phpunit

Unit tests php (юнит-тестирование php)

Структура проекта обычно предполагает размещение тестов в каталоге tests/ и использование автозагрузки PSR-4. Минимальный тест:


<?php
use PHPUnit\Framework\TestCase;

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

система тестирования php (система тестирования на php)

PHPUnit 10.0.0 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 0.001s, Memory: 6.00 MB

OK (1 test, 1 assertion)

Api tests php (тестирование api на php)

Цель использования: обеспечить регрессионное тестирование отдельных компонентов (классов, функций) с высокой скоростью и повторяемостью. Сценарии: TDD, CI проверки перед деплоем, покрытие кода.

Типичная ошибка: метод теста не начинается с test или не имеет аннотации @test. PHPUnit не распознает его как тест и пропускает. Решение: явно указывать префикс или использовать атрибут #[\PHPUnit\Framework\Attributes\Test] в PHP 8.

Еще одна проблема: зависимость от внешних ресурсов (БД, API) без изоляции. При каждом запуске теста данные должны быть предсказуемы. Решение: применять тестовые заглушки (mock-объекты) или фикстуры в setUp().

Как написать параметризованные тесты с PHPUnit Data Providers?

Для проверки одной функции на множестве входных данных используйте Data Provider – метод, возвращающий массив наборов. Пример:


<?php
class CalculatorTest extends TestCase
{
    /** @dataProvider additionProvider */
    public function testAddition($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, -2],
        ];
    }
}
    

Http localhost test php (тестирование php на localhost)

PHPUnit 10.0.0 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 0.001s, Memory: 6.00 MB

OK (3 tests, 3 assertions)

Php testing (тестирование php)

Цель: сократить дублирование кода и охватить граничные случаи. Используется, когда логика однотипна, но данные различны.

Проблема: если Data Provider возвращает слишком большой массив, тесты становятся медленными. Решение: для интеграционных тестов ограничить количество кейсов, а для unit – допустимо много.

Как организовать функциональное тестирование с эмуляцией браузера с помощью Codeception?

Codeception позволяет писать acceptance-тесты, имитирующие клики по кнопкам, заполнение форм и проверку ответа сервера. Установка:

composer require --dev codeception/codeception

Инициализация для веб-приложения:

vendor/bin/codecept bootstrap

Пример acceptance-теста для логина:


<?php
$I = new AcceptanceTester($scenario);
$I->amOnPage('/login');
$I->fillField('username', 'admin');
$I->fillField('password', 'secret');
$I->click('Login');
$I->see('Dashboard');
    

Цель: проверка пользовательских сценариев без реального браузера (через PhpBrowser или Guzzle). Часто используется для smoke-тестов после деплоя.

Частая ошибка: Codeception не видит элементы на странице из-за JavaScript. Решение: включить модуль WebDriver (Selenium) для тестов с JS.

Как описать поведение класса через спецификации с PHPSpec?

PHPSpec следует методологии BDD: сначала пишется спецификация (что класс должен делать), затем код. Установка:

composer require --dev phpspec/phpspec

Создание спецификации для класса User:


<?php
class UserSpec extends ObjectBehavior
{
    function it_should_set_full_name()
    {
        $this->setFirstName('John');
        $this->setLastName('Doe');
        $this->getFullName()->shouldReturn('John Doe');
    }
}
    

Затем генерируется класс User через vendor/bin/phpspec run.

Цель: разработка через поведение (BDD), когда спецификация становится документацией. Подходит для моделей предметной области.

Ошибка: PHPSpec требует строгой структуры папок и имен. Если спецификация не найдена, проверьте соглашение {ClassName}Spec в каталоге spec/.

Как использовать SimpleTest для быстрого прототипирования тестов?

SimpleTest старее PHPUnit, но иногда быстрее разворачивается (без Composer). Библиотека лежит как отдельный файл. Пример:


<?php
require_once 'simpletest/autorun.php';

class TestCalculator extends UnitTestCase
{
    function testAddition() {
        $calc = new Calculator();
        $this->assertEqual(4, $calc->add(2, 2));
    }
}
    

Цель: минималистичное тестирование без Composer, например, для легаси-проектов.

Проблема: SimpleTest не обновляется, отсутствует поддержка современных возможностей PHP 8. Лучше мигрировать на PHPUnit.

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

Интеграционное тестирование с PHPUnit и SQLite

Для тестирования запросов к БД без внешней базы данных используйте SQLite in-memory. Пример теста для репозитория UserRepository:

Пример

<?php
use PHPUnit\Framework\TestCase;

class UserRepositoryTest extends TestCase
{
    private PDO $pdo;
    private UserRepository $repository;

    protected function setUp(): void
    {
        $this->pdo = new PDO('sqlite::memory:');
        $this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
        $this->repository = new UserRepository($this->pdo);
    }

    public function testFindByName()
    {
        $this->pdo->exec("INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')");
        $user = $this->repository->findByName('Alice');
        $this->assertEquals('Alice', $user->getName());
    }

    public function testInsertGeneratesId()
    {
        $newUser = new User('Bob', 'bob@example.com');
        $id = $this->repository->insert($newUser);
        $this->assertIsInt($id);
        $this->assertGreaterThan(0, $id);
    }
}
PHPUnit 10.0.0 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 0.002s, Memory: 6.50 MB

OK (2 tests, 2 assertions)

Пояснение: метод setUp() создает чистую таблицу для каждого теста. После выполнения теста SQLite в памяти уничтожается. Это изолирует тесты друг от друга.

Тестирование API с Codeception

Допустим, ваше приложение отдает JSON по адресу /api/users/1. Acceptance-тест с модулем REST:

Пример

<?php
class ApiCest
{
    public function tryToGetUser(ApiTester $I)
    {
        $I->sendGET('/api/users/1');
        $I->seeResponseCodeIs(200);
        $I->seeResponseIsJson();
        $I->seeResponseContainsJson(['id' => 1, 'name' => 'Alice']);
    }

    public function tryToCreateUser(ApiTester $I)
    {
        $I->sendPOST('/api/users', ['name' => 'Bob', 'email' => 'bob@example.com']);
        $I->seeResponseCodeIs(201);
        $I->seeResponseIsJson();
        $I->seeResponseMatchesJsonType([
            'id' => 'integer:>0',
            'name' => 'string',
        ]);
    }
}
vendor/bin/codecept run acceptance

Time: 1.23 s, Memory: 10.00 MB

2 passed

Пояснение: модуль REST автоматически эмулирует HTTP запросы. Можно тестировать как REST, так и SOAP (модулем PhpBrowser).

Тестирование с использованием заглушек (Mocking) в PHPUnit

Заглушки заменяют реальные зависимости для изоляции теста. Пример теста для сервиса, который отправляет email:

Пример

<?php
class MailServiceTest extends TestCase
{
    public function testSendWelcomeEmailCallsMailer()
    {
        $mailer = $this->createMock(MailerInterface::class);
        $mailer->expects($this->once())
               ->method('send')
               ->with($this->stringContains('welcome'));

        $service = new WelcomeEmailService($mailer);
        $service->sendTo(new User('Alice', 'alice@example.com'));
    }
}
PHPUnit 10.0.0 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 0.001s, Memory: 6.00 MB

OK (1 test, 1 assertion)

Пояснение: метод createMock создает объект-заглушку, проверяющую вызов send с нужным аргументом. Это позволяет тестировать логику сервиса без реальной отправки писем.

Нестандартный пример: тестирование исключений в PHPUnit

Убедитесь, что код выбрасывает исключение при неверном вводе.

Пример

<?php
class CalculatorTest extends TestCase
{
    public function testDivideByZeroThrowsException()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Division by zero is not allowed');

        $calc = new Calculator();
        $calc->divide(10, 0);
    }
}
PHPUnit 10.0.0 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 0.001s, Memory: 6.00 MB

OK (1 test, 1 assertion)

Пояснение: вызов expectException регистрирует ожидание. Тест считается пройденным, если исключение было выброшено.

Система тестирования на PHP - comments

En
система тестирования php (php)