В этой записи, мы обсудим тестирование в Yii2 basic с помощью Codeception и почему это пригодиться.
Эта запись относится к курсу Yii2 basic.
Вам нужно привыкнуть к написанию тестов, потому что они вам помогут в плане сокращения времени на ручное тестирование, особенно когда функционал вашего сайта значительно вырастет.
Codeception
В Yii2 используется Codeception как framework для тестирования.
Codeception — это отдельный проект, не зависящий от Yii2, который используется во многих современных framework’ах (Symfony, Laravel и др.). И конечно же, вы можете установить Codeception используя Composer.
Структура тестов, папка tests и её содержимое
Для написания тестов, существует специальная папка tests
.
Если вы в неё зайдет, то увидите следующую структуру:
_data _output _support acceptance bin functional unit _bootstrap.php acceptance.suite.yml.example functional.suite.yml unit.suite.yml
_data
— это папка, в которой хранятся PHP файлы возвращающие массив данных (например, автомобили) для использования вfixtures
.fixtures
класс по типу ActiveRecord, который перед запуском теста, добавляет данные из файла, в заранее указанную таблицу из ActiveRecord основного класса (используя параметр$modelClass
, например,fixtures/Car.php
, будет ниже в примере). После того как тест будет завершен, все данные из этой таблицы будут удалены. И так процесс будет повторяться._output
— это папка, в которой выводится результат запуска функциональных тестов. Выглядят они как исходный код HTML странички. Например, если вы зайдете на страницу и посмотрите на неё с помощью инспектора кода, в этом случае вы увидите тоже самое._support
— это сгенерированные модули, которые нужны для запуска тестов. Они создаются с помощью команды (vendor/bin/codecept build tests/
)bin
— это папка, в которой находится бинарный файл (yii), на подобии того, что лежит в корне, только этот именно для запуска процессов в тестовом режиме.acceptance
— это папка, в которой находятся файлы со сценарием со стороны пользователя, эмулируя поведение браузераfunctional
— это папка, в которой находятся файлы со сценарием со стороны пользователя, эмулируя поведение браузераunit
— этот папка с файлами, в которых проверяются классы или часть кода на то, что он/они работает по определенному сценарию или без сценария
Запуск тестов
В Yii2 по умолчанию присутствуют некоторые тесты запустить тест и чтобы их запустить — используйте команду ниже:
$ cd /var/www/yii-dev/ $ vendor/bin/codecept run
Если у вас выдает следующую ошибку:
[yii\base\InvalidConfigException] Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required.
Тогда её можно исправить установив php-gd
расширение:
- Для php 5.5+:
$ sudo apt-get install php5-gd
- Для php 7+:
$ sudo apt-get install php7.0-gd
И теперь снова попробуйте запустить тесты, у вас должно вывестись что-то похожее на это:
Собственные тесты для класса Car
Теперь пришло время написать собственные тесты. У нас есть класс Car
, у которого уже есть свой controller и action’ы. Тем самым, чтобы нам каждый раз не проверять работает добавление машины или нет, мы можешь запускать тесты, которые сделают все повторяющуюся работу за нас.
Обратите внимание, что я буду рассматривать только unit тесты для тестирования модели Car. Если вам нужно проверять как это работает на клиентской части (контроллеры, доступна страница или нет) — используйте functional и acceptance тесты. Functional используется больше для тестирования API, где acceptance использует настоящий браузер (например, Firefox) — что в точности эмулирует настоящего клиента.
Список unit тестов, который мне пришел на ум:
- добавление новой машины
- могу ли я добавить машину не заполняя все поля формы
- удаление существующей машины
- изменение существующей машины
Для этого мы создадим новый тест формата — unit
.
Сделаем это через консоль:
$ cd /var/www/yii-dev $ vendor/bin/codecept generate:test unit models/Car
generate:test
— хотим создать новый тестunit
— тип тестаmodels/Car
—models
— это папка, внутриunit
папки, иCar
— это название теста
После создания теста, у вас должно написать следующее сообщение:
Test was created in /var/www/yii-dev/tests/unit/models/CarTest.php
Это означает, что тест был успешно создан. Обратите внимание, что к введенному Car
был автоматически добавлено «Test». В итоге, у нас получается следующее название файла CarTest.php
внутри tests/unit/models
.
Файл должен содержать следующее:
Внутренность файла по умолчанию может измениться со временем, поэтому не пугайтесь, если у вас что-то выглядит по другому.
<?php namespace models; class CarTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testSomeFeature() { } }
$tester
будет использоваться для обращения к тестеру (виртуально созданному тестировщику — он создается при каждом запуске теста). У него так же есть функции помощники (управления fixture’ами и другие (то есть загружать нужную таблицу данными), о них чуть позже).before()
мы будем использоваться для того, чтобы объявить какие-либо данные перед запуском тестов. Например загрузитьfixtures
, которые я ранее описывал. Сейчас нам нужно будет создать новый файл в папке_data
.
Назовите файл cars.php
и добавьте туда следующее:
<?php return [ [ 'name' => 'test1', 'model' => 'bmw' ], [ 'name' => 'test2', 'model' => 'mercedes' ] ];
Этот файл будет подгружаться каждый раз когда будем запускать
CarTest.php
. Файлcars.php
представляет из себя обычный массив, но на самом деле — он будет добавлен в таблицуcars
в виде обычного SQL, поэтому поля по типуname
иmodel
должны совпадать со названиями колонок в таблице. После того как тест завершиться, все данные из таблицыcars
удаляться и так процесс будет повторяться. Вы можете добавить безграничное кол-во машин. Я буду использовать всего парочку, потому что больше и не требуется.
_after()
мы использовать не будем, поэтому его можете удалить.
Все остальные методы, которые начинаются с testNazvanieMetoda()
— это методы, которые будут проверяться во время запуска тестирования. test
— это обязательная часть названия метода, иначе он будет проигнорирован и последующие слова — это и есть название теста (NazvanieMetoda
). testSomeFeature()
вы тоже можете удалить, потому что это бесполезный стандартный тест.
Старайтесь использовать английский в названиях теста — это выглядит красивее, легче читается и в дальнейшем, если ваша команда будет расти, в ней могут появится англоговорящие коллеги, которые не смогут читать ваш код.
Теперь создадим собственные методы для тестов.
Возьмем заранее подготовленный список тестов и придумаем им название.
У меня получилось следующее:
-
testCreate()
— добавление новой машины testCreateEmptyFormSubmit()
— могу ли я добавить машину не заполняя все поля формыtestDelete()
— удаление машиныtestUpdate()
— изменение машины
Теперь создадим пустые методы для тестов. Наш класс будет выглядеть следующим образом:
<?php namespace models; class CarTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; protected function _before() { } /** * Тест для добавление машины */ public function testCreate() { } /** * Форма должна выдать ошибку, если пытаюсь отправить пустую форму */ public function testCreateEmptyFormSubmit() { } /** * Возможность удалить машину */ public function testDelete() { } /** * Возможность изменить машину */ public function testUpdate() { } }
Теперь добавим fixture в метод __before()
(которую мы создали в папку _data
, файл cars.php
), чтобы перед запуском теста у нас было пару машин, которые мы можем сравнивать с чем-то если нам это понадобиться.
Так же нужно создать папку fixtures
в корне проекта, там где лежат папки models
и controllers
и другие и внутри создать класс Car.php
со следующим содержимым:
<?php namespace app\fixtures; use yii\test\ActiveFixture; class Car extends ActiveFixture { public $modelClass = 'app\models\Car'; }
Теперь мы можешь использовать Car
fixture, внутри __before()
следующим образом:
... protected function _before() { $this->tester->haveFixtures([ 'cars' => [ 'class' => CarFixture::className(), 'dataFile' => codecept_data_dir() . 'cars.php' ] ]); } ...
Но так же не забудьте добавить fixtures
в список зависимостей Codeception
в файле unit.suite.yml
, который находится в папке tests
.
Замените:
part: [orm, email]
на
part: [orm, email, fixtures]
И введите следующую команды в консоли, чтобы подгрузить нужные конфиги и конмпоненты для тестирования:
$ cd /var/www/yii-dev/ $ vendor/bin/codecept build
У вас должно вывестись, что-то похожее на это:
Building Actor classes for suites: functional, unit -> FunctionalTesterActions.php generated successfully. 0 methods added \FunctionalTester includes modules: Filesystem, Yii2 -> UnitTesterActions.php generated successfully. 0 methods added \UnitTester includes modules: Asserts, Yii2
Сейчас если вы запустите тесты:
$ vendor/bin/codecept run
То вероятнее всего, что у вас посыпятся следующие ошибки:
[yii\db\Exception] SQLSTATE[42000] [1049] Unknown database 'yii2_basic_tests'
Это ошибка говорит о том, что вы не изменили стандартную базу данных для тестирования в конфиге config/test_db.php
:
Поэтому открываем файл, заменяем yii2_basic_tests
на yii_loc_test
и сохраняем.
- Теперь нам нужно зайти в базу данных через phpMyAdmin, открыть
yii_loc
и сделать «Экспорт». - Оставьте «Быстрый» вариант и нажмите «ОК»
- Скачиваете файл
- В phpMyAdmin нажмите на логотип, далее на «Базы данных» и введите новое название
yii_loc_test
- Зайдите в только что созданную базу данных и нажмите «Импорт»
- Выберите файл
yii_loc.sql
и нажмите «ОК» - База должна быть экспортирована
Теперь попробуйте снова запустить тесты:
$ vendor/bin/codecept run
Все они должны быть отмечены зеленой галочкой — это означает, что все прошло успешно.
Теперь самое время написать логику для наших тестов. Я буду брать метод за методом и расписывать как и почему я его написал именно так.
- Тест
testCreate()
:
/** * Тест для добавление машины */ public function testCreate() { $car = new Car(); $car->name = 'Название'; $car->model = 'bmw'; expect_that($car->save()); }
Все что мы тут делаем — это создаем новый объект с данными используя класс Car
, вносим в него данные машины (name
и model
) и сохраняем. Функция expect_that()
(на рус. можно перевести как — «ожидаю что») принимает в себя булевое значение. Она проверяет на правдопобность действия. То есть я жду, что save()
вернет true
, потому что машина должна быть добавлена. Если в дальнейшем, я что-то подправлю в модели, то этот тест провалиться и я об этом узнаю, потому что я буду запускать тесты каждый раз, когда у меня будут нововведения.
- Тест
testCreateEmptyFormSubmit()
:
/** * Форма должна выдать ошибку, если пытаюсь отправить пустую форму */ public function testCreateEmptyFormSubmit() { $car = new Car(); expect_not($car->validate()); expect_not($car->save()); }
Этот тест очень простой. Он проверяет разрешает ли модель Car
отправлять пустые данные в базу данных (Car
— это не должна делать, потому что такие поля как name
и model
являются обязательными).
И тем самым мы ожидаем, что форма не должна пройти проверку expect_not($car->validate())
и что она не должна (на рус. expect_not
переводится как «ожидаю, что нет») сохраниться expect_not($car->save())
.
- Тест
testDelete()
:
/** * Возможность удалить машину */ public function testDelete() { $car = Car::findOne(1); expect_that($car !== null); expect_that($car->delete()); }
В этом тесте, вы находим первую запись в таблице — это test1
из tests/_data/cars.php
. Сначала ожидаем, что мы найдем машину (expect_that($car !== null)
) и дальше, что она будет успешно удалена (expect_that($car->delete())
).
- Тест
testUpdate()
:
/** * Возможность изменить машину */ public function testUpdate() { $car = Car::findOne(['name' => 'test1']); expect_that($car !== null); $car->name = 'mashina_ne_sushestvuet'; expect_that($car->save()); $updatedCar = Car::findOne(['name' => 'mashina_ne_sushestvuet']); expect_that($updatedCar !== null); }
В этой тесте нам нужно найти машину, проверить удалось ли нам её найти (expect_that($car !== null)
), далее поменять ей название, проверить удалось ли нам обновить его (expect_that($car->save())
), далее найти машину по обновленному имени и если успешно, значит старое название было изменено на новое (expect_that($updatedCar !== null)
).
Послесловие
Со временем кол-во функционала в вашем проекте будет увеличиваться и лучше всего — это когда вы будите писать тесты на каждую новую нововведение, чтобы потом не писать огромное кол-во тестов за раз.
Читать далее — Yii2 basic: Логи. Как их делать и почему они нужны?