Codeception для PHP: полное руководство с примерами
1. Что это и зачем нужна
Codeception — это полнофункциональный фреймворк для тестирования PHP-приложений, который объединяет в себе лучшие практики модульного, функционального, приемочного (acceptance) и интеграционного тестирования. В отличие от стандартного PHPUnit, Codeception предоставляет единый, интуитивно понятный API для всех типов тестов, а также мощные инструменты для работы с браузерами (через WebDriver или PhpBrowser), базами данных, REST API и даже JavaScript.
Основная цель Codeception — сделать процесс написания тестов максимально простым и читаемым. Фреймворк использует BDD-подобный синтаксис (Behavior-Driven Development), где тесты описываются на естественном языке с помощью методов-действий: $I->amOnPage('/login'), $I->fillField('email', 'user@example.com'), $I->click('Login'). Это позволяет не только разработчикам, но и тестировщикам, аналитикам и менеджерам легко понимать, что именно проверяется.
Codeception незаменим в проектах, где требуется комплексное тестирование: от проверки отдельных классов (unit-тесты) до симуляции действий реального пользователя в браузере (acceptance-тесты). Он поддерживает популярные фреймворки (Laravel, Symfony, Yii2, Zend), автоматически управляет транзакциями базы данных, умеет делать скриншоты при ошибках и генерировать подробные отчёты. Если вы хотите повысить качество кода и сократить время на регрессионное тестирование — Codeception станет вашим надёжным помощником.
2. Установка
Codeception распространяется через Composer — стандартный менеджер пакетов для PHP. Убедитесь, что у вас установлен PHP версии 7.4 или выше и Composer.
Выполните в терминале в корне вашего проекта:
composer require --dev codeception/codeception
После установки вы можете инициализировать Codeception в проекте:
vendor/bin/codecept init
Эта команда создаст базовую структуру директорий: tests/ с подпапками unit, functional, acceptance, а также файл конфигурации codeception.yml.
Для работы с браузерными тестами дополнительно установите модуль WebDriver (если планируете использовать Selenium или ChromeDriver):
composer require --dev codeception/module-webdriver
Для тестирования REST API установите модуль REST:
composer require --dev codeception/module-rest
3. Быстрый старт — минимальный рабочий пример
Создадим простой unit-тест для проверки математической функции. После инициализации (codecept init) у вас уже есть файл tests/unit.suite.yml. Отредактируем его, чтобы подключить модуль Asserts:
# tests/unit.suite.yml
actor: UnitTester
modules:
enabled:
- Asserts
Теперь создадим тест-файл tests/unit/MathTest.php:
<?php
use Codeception\Test\Unit;
class MathTest extends Unit
{
protected UnitTester $tester;
public function testAddition()
{
$result = 2 + 3;
$this->tester->assertEquals(5, $result, '2 + 3 должно быть равно 5');
}
public function testSubtraction()
{
$result = 10 - 4;
$this->tester->assertGreaterThan(5, $result, '10 - 4 должно быть больше 5');
}
}
Запустим тест:
vendor/bin/codecept run unit
Вы должны увидеть зелёный отчёт с двумя успешными тестами. Поздравляю, вы написали первый тест на Codeception!
4. Основные методы/функции/классы
Ниже приведены ключевые методы, которые вы будете использовать чаще всего. Они доступны через объект $I (Actor) в каждом типе тестов.
4.1. amOnPage()
Сигнатура: void amOnPage(string $page)
Что делает: Переходит на указанный URL относительно базового URL, заданного в конфигурации. Используется в acceptance- и functional-тестах.
<?php
// В acceptance-тесте
$I->amOnPage('/login');
$I->see('Войти'); // Проверяем, что на странице есть текст "Войти"
4.2. fillField()
Сигнатура: void fillField(string|array $field, string $value)
Что делает: Заполняет текстовое поле (input, textarea) указанным значением. Поле можно найти по имени, ID, CSS-классу или XPath.
<?php
$I->amOnPage('/register');
$I->fillField('input[name="email"]', 'test@example.com');
$I->fillField('#password', 'securePass123');
4.3. click()
Сигнатура: void click(string|array $link, array $context = [])
Что делает: Имитирует клик по кнопке, ссылке или любому другому элементу. Можно указать текст, CSS-селектор или XPath.
<?php
$I->click('Submit'); // Клик по кнопке с текстом "Submit"
$I->click('#login-button'); // Клик по элементу с ID login-button
$I->click('a[href="/logout"]'); // Клик по ссылке
4.4. see()
Сигнатура: void see(string $text, ?string $selector = null)
Что делает: Проверяет, что на текущей странице присутствует указанный текст. Если передан селектор, проверка выполняется только внутри этого элемента.
<?php
$I->amOnPage('/dashboard');
$I->see('Добро пожаловать'); // Текст есть на странице
$I->see('Ошибка', '.alert-danger'); // Текст внутри элемента с классом alert-danger
4.5. dontSee()
Сигнатура: void dontSee(string $text, ?string $selector = null)
Что делает: Обратный метод к see() — проверяет, что текст отсутствует на странице.
<?php
$I->amOnPage('/login');
$I->dontSee('Ошибка авторизации'); // Убеждаемся, что сообщения об ошибке нет
4.6. grabFromCurrentUrl()
Сигнатура: string grabFromCurrentUrl(?string $pattern = null)
Что делает: Возвращает текущий URL или его часть, соответствующую регулярному выражению. Полезно для проверки редиректов.
<?php
$I->amOnPage('/old-page');
$I->seeCurrentUrlEquals('/new-page'); // Проверка полного URL
$id = $I->grabFromCurrentUrl('/\/user\/(\d+)/'); // Извлечь ID пользователя из URL
4.7. haveInDatabase()
Сигнатура: int haveInDatabase(string $table, array $data)
Что делает: Вставляет запись в базу данных (требуется модуль Db). Возвращает ID вставленной записи. Используется для подготовки тестовых данных.
<?php
// В functional-тесте
$userId = $I->haveInDatabase('users', [
'email' => 'test@example.com',
'name' => 'John Doe'
]);
$I->amOnPage('/user/' . $userId);
$I->see('John Doe');
4.8. sendAjaxPostRequest()
Сигнатура: void sendAjaxPostRequest(string $uri, array $params = [])
Что делает: Отправляет AJAX POST-запрос на указанный URI. Полезно для тестирования API-эндпоинтов, работающих через JavaScript.
<?php
$I->sendAjaxPostRequest('/api/task', ['title' => 'New Task']);
$I->seeResponseCodeIs(201); // Проверка HTTP-статуса
$I->seeResponseContains('"id":'); // Проверка JSON-ответа
4.9. seeInDatabase()
Сигнатура: void seeInDatabase(string $table, array $criteria)
Что делает: Проверяет, что в базе данных существует запись, соответствующая указанным критериям.
<?php
$I->amOnPage('/register');
$I->fillField('email', 'new@user.com');
$I->click('Register');
$I->seeInDatabase('users', ['email' => 'new@user.com']); // Подтверждаем, что пользователь создан
4.10. executeInSelenium()
Сигнатура: mixed executeInSelenium(\Closure $function)
Что делает: Выполняет произвольный код через WebDriver (Selenium). Позволяет получить доступ к низкоуровневым функциям браузера, например, выполнить JavaScript.
<?php
$I->executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {
$title = $webdriver->getTitle();
$webdriver->manage()->window()->maximize();
return $title;
});
5. Реальный практический пример (100-200 строк кода)
Рассмотрим тестирование процесса регистрации пользователя на сайте. Мы создадим acceptance-тест, который симулирует действия реального пользователя: открывает страницу регистрации, заполняет форму, отправляет её и проверяет результат.
Предположим, у нас есть Laravel-приложение. Сначала настроим tests/acceptance.suite.yml:
# tests/acceptance.suite.yml
actor: AcceptanceTester
modules:
enabled:
- WebDriver:
url: 'http://localhost:8000'
browser: chrome
window_size: 1280x1024
- Db:
dsn: 'mysql:host=localhost;dbname=testdb'
user: 'root'
password: ''
populate: true
cleanup: true
Теперь сам тест tests/acceptance/RegistrationCest.php:
<?php
use Codeception\Util\Fixtures;
class RegistrationCest
{
private string $testEmail = 'user_' . uniqid() . '@example.com';
private string $testPassword = 'SecurePass123!';
/**
* @before
*/
public function _before(AcceptanceTester $I)
{
// Убедимся, что пользователь не залогинен
$I->amOnPage('/logout');
}
public function testSuccessfulRegistration(AcceptanceTester $I)
{
// 1. Открываем страницу регистрации
$I->amOnPage('/register');
$I->see('Регистрация', 'h1');
// 2. Заполняем форму
$I->fillField('input[name="name"]', 'Иван Петров');
$I->fillField('input[name="email"]', $this->testEmail);
$I->fillField('input[name="password"]', $this->testPassword);
$I->fillField('input[name="password_confirmation"]', $this->testPassword);
// 3. Отправляем форму
$I->click('button[type="submit"]');
// 4. Проверяем, что пользователь перенаправлен на дашборд
$I->seeCurrentUrlEquals('/dashboard');
$I->see('Добро пожаловать, Иван Петров!');
// 5. Проверяем, что в БД появилась запись
$I->seeInDatabase('users', [
'email' => $this->testEmail,
'name' => 'Иван Петров'
]);
// 6. Проверяем, что письмо с подтверждением отправлено (если есть)
$I->seeInDatabase('email_log', [
'recipient' => $this->testEmail,
'subject' => 'Подтверждение регистрации'
]);
}
public function testRegistrationWithExistingEmail(AcceptanceTester $I)
{
// Сначала создадим пользователя через БД
$existingEmail = 'existing@example.com';
$I->haveInDatabase('users', [
'email' => $existingEmail,
'name' => 'Existing User',
'password' => password_hash('test123', PASSWORD_DEFAULT)
]);
// Пытаемся зарегистрироваться с тем же email
$I->amOnPage('/register');
$I->fillField('name', 'New User');
$I->fillField('email', $existingEmail);
$I->fillField('password', 'NewPass123!');
$I->fillField('password_confirmation', 'NewPass123!');