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

· Yii2 basic и Курсы · 11 мин чтения

В этой записи, мы обсудим тестирование в Yii2 basic с помощью Codeception и почему это пригодиться. 

Эта запись относится к курсу Yii2 basic.

Вам нужно привыкнуть к написанию тестов, потому что они вам помогут в плане сокращения времени на ручное тестирование, особенно когда функционал вашего сайта значительно вырастит.

Codeception

В Yii2 используется Codeception как framework для тестирования.

Codeception — это отдельный проект, не зависящий от Yii2, который используется во многих современных framework’ах (Symfony, Laravel и др.). Используя Composer, вы можете установить Codeception куда угодно.

Структура тестов, папка 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.

Сделаем это через консоль:

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

Комментарии