Если вы пишете на Yii, но до сих пор не покрываете код тестами, вы рискуете. Однажды после правки в контроллере отвалится отправка почты, а вы узнаете об этом от клиента. Тесты — это не роскошь, а страховка. В этой статье разберём, как настроить PHPUnit в Yii-приложении, писать тесты для моделей, контроллеров и даже для базы данных. Будет много примеров и минимум воды.
Зачем вообще тестировать Yii-приложение
Yii — фреймворк, который сам по себе довольно строгий. Он заставляет вас следовать MVC, использовать ActiveRecord, валидаторы. Но даже самая аккуратная архитектура не спасает от человеческих ошибок. Тесты дают уверенность: вы меняете код, запускаете пару команд — и видите, что ничего не сломалось. Это как проверить, выключили ли утюг, перед выходом из дома. Только для кода.
Кроме того, тесты — это живая документация. Глядя на тест, новый разработчик быстрее поймёт, как работает метод. И да, с тестами рефакторинг перестаёт быть страшным сном.
Установка и настройка PHPUnit в Yii
Yii не требует PHPUnit из коробки, но в шаблоне advanced и basic он уже прописан в composer.json. Если вы используете свой проект, добавьте пакет:
composer require --dev phpunit/phpunit ^9.5
Для Yii 2.0 актуальны версии PHPUnit 8 или 9. Не берите PHPUnit 10, если не хотите возиться с несовместимостью. После установки создайте файл phpunit.xml в корне проекта. Пример для basic-шаблона:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Unit Tests">
<directory>tests/unit</directory>
</testsuite>
<testsuite name="Functional Tests">
<directory>tests/functional</directory>
</testsuite>
</testsuites>
</phpunit>
Теперь создайте папки tests/unit и tests/functional. В каждой — файл bootstrap.php, который подключает Yii-приложение. Для unit-тестов достаточно автозагрузчика, но если тестируете модели с БД, лучше поднять консольное приложение.
Пишем первый юнит-тест для модели
Начнём с простого. У вас есть модель User с методом getFullName(). Хотим проверить, что он возвращает имя и фамилию через пробел.
namespace tests\unit\models;
use app\models\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function testGetFullName()
{
$user = new User();
$user->firstName = 'Ivan';
$user->lastName = 'Petrov';
$this->assertEquals('Ivan Petrov', $user->getFullName());
}
}
Запускаем: ./vendor/bin/phpunit tests/unit/models/UserTest.php. Если всё ок, увидите зелёную точку. Если нет — красную F и сообщение об ошибке. Просто, правда?
Но часто модели используют подключение к БД. Для таких тестов нужно настроить фикстуры и очистку. Yii предоставляет класс yii\codeception\fixtures\ActiveFixture, но с PHPUnit это работает через наследование от yii\test\TestCase (в Yii 2.0 есть пакет yiisoft/yii2-test). Установите его:
composer require --dev yiisoft/yii2-test
Теперь ваш тест-класс может наследоваться от yii\test\TestCase, который автоматически подключает Yii-приложение и предоставляет методы для работы с БД.
Функциональные тесты: проверяем контроллеры и ответы
Юнит-тесты проверяют отдельные методы. Функциональные — весь запрос-ответ. В Yii для этого есть встроенный класс yii\test\WebTestCase (или FunctionalTester в Codeception, но мы же про PHPUnit).
Пример теста для действия actionIndex в контроллере SiteController:
namespace tests\functional;
use yii\test\WebTestCase;
class SiteControllerTest extends WebTestCase
{
public function testIndexPageReturnsSuccess()
{
$this->get('site/index');
$this->assertResponseCode(200);
$this->assertSee('Welcome');
}
}
Здесь $this->get() эмулирует GET-запрос, assertResponseCode проверяет HTTP-код, а assertSee ищет текст на странице. Всё как в реальном браузере, только без мышки.
Работа с базой данных: фикстуры и транзакции
Самое больное место — тесты, которые пишут в БД. Чтобы не засорять базу, используйте транзакции. Yii\test\TestCase оборачивает каждый тест в транзакцию и откатывает её после завершения. Но есть нюанс: если тестируете код, который сам управляет транзакциями, откат может не сработать. Тогда используйте фикстуры.
Фикстуры — это наборы данных, которые загружаются перед тестом. В Yii они описываются в классах, наследующих yii\test\Fixture. Пример:
namespace tests\fixtures;
use yii\test\Fixture;
class UserFixture extends Fixture
{
public $dataFile = '@tests/fixtures/data/user.php';
public $depends = [];
}
В файле user.php возвращаем массив с данными. Потом в тесте указываем:
public $fixtures = [
'users' => UserFixture::class,
];
Перед тестом таблица user заполнится, после — очистится. Удобно и предсказуемо.
Вот таблица, которая поможет выбрать стратегию работы с данными:
| Метод | Когда использовать | Плюсы | Минусы |
|---|---|---|---|
| Транзакции | Простые тесты без вложенных транзакций | Быстро, автоматический откат | Не работает с DDL и вложенными транзакциями |
| Фикстуры | Сложные данные, несколько таблиц | Гибко, можно переиспользовать | Медленнее, нужно писать классы |
| Ручная очистка | Экзотические случаи | Полный контроль | Легко забыть, данные остаются |
Полезные советы и частые грабли
За годы работы с Yii и PHPUnit я собрал несколько правил, которые экономят нервы.
- Не тестируйте фреймворк. Если вы проверяете, что Yii правильно сохраняет модель, — это пустая трата времени. Тестируйте свою логику: валидацию, вычисления, бизнес-правила.
- Изолируйте тесты. Один тест — одна проверка. Если тест упал, вы должны сразу понять, что именно сломалось.
- Используйте dataProvider для однотипных проверок. В PHPUnit есть аннотация @dataProvider. Она позволяет прогнать один тест с разными входными данными.
Пример dataProvider:
public function providerForFullName()
{
return [
['Ivan', 'Petrov', 'Ivan Petrov'],
['', 'Petrov', 'Petrov'],
['Ivan', '', 'Ivan'],
];
}
/**
* @dataProvider providerForFullName
*/
public function testGetFullNameWithDifferentData($first, $last, $expected)
{
$user = new User();
$user->firstName = $first;
$user->lastName = $last;
$this->assertEquals($expected, $user->getFullName());
}
Ещё один момент: не забывайте про setUp() и tearDown(). В setUp() можно создавать объекты, которые нужны всем тестам, а в tearDown() — чистить за собой. Но не злоупотребляйте: если объект нужен только одному тесту, создавайте его внутри метода.
Что в итоге
Тесты для Yii с PHPUnit — это не страшно, а удобно. Вы один раз настраиваете phpunit.xml, пишете пару тестов для моделей, потом добавляете функциональные. Постепенно покрытие растёт, и вы начинаете спать спокойнее. Главное — не гнаться за 100% покрытием. Тестируйте критичные участки: авторизацию, платежи, отправку форм. Остальное — по желанию.
Помните: тесты — это инвестиция в будущее. Чем раньше начнёте, тем меньше боли при рефакторинге. Удачи в написании зелёных тестов!



