Yii2 basic: Пишем и запускаем тесты с Codeception

· Yii2 basic · 11 мин чтения

В этой записи, мы обсудим тестирование в 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

И теперь снова попробуйте запустить тесты, у вас должно вывестись что-то похожее на это:

Yii2 basic успешное тестирование

Собственные тесты для класса 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: Логи. Как их делать и почему они нужны?