Сервис на Yii2: Тестирование приложения с Codeception

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

Предыдущие части | Исходники на GitHub | Скринкаст о тестировании

Приятно при написании кода время от времени запускать автотесты и наблюдать, что весь код приложения работает корректно:

Codeception PHP Testing Framework v2.0.16
Powered by PHPUnit 4.7.7 by Sebastian Bergmann and contributors.

Unit Tests (26) -------------------------------------------------------------------------------
Test validate empty new password (UserTest::testValidateEmptyNewPassword)                   Ok
Test validate wrong new password (UserTest::testValidateWrongNewPassword)                   Ok
...
Test correct signup (SignupFormTest::testCorrectSignup)                                     Ok
Test not correct signup (SignupFormTest::testNotCorrectSignup)                              Ok
-----------------------------------------------------------------------------------------------

Time: 7.37 seconds, Memory: 39.75Mb

OK (27 tests, 74 assertions)

Все вопросы по их структуре, нюансам написания, используемым инструментам и лучшим практикам мы рассмотрели в вебинаре о тестировании. Так что если вы не сталкивались с этим ранее, то вам сначала нужно посмотреть тот скринкаст.

Итак, начнём с банального.

Разделение конфигурации

По умолчанию в yii2-app-basic нет разделения файлов конфигурации на общие и локальные. Имеется всего такой набор:

config
├── console.php
├── web.php
├── params.php
└── db.php

Это неудобно при использовании систем контроля версий

В первой части мы переработали файлы по примеру yii2-app-advanced, добавив наследование конфигурации:

config
├── common.php
├── common-local.php
├── console.php
├── console-local.php
├── web.php
├── web-local.php
├── params.php
└── params-local.php

И в файле web/index.php склеиваем нужный комплект вместе:

$config = yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../config/common.php'),
    require(__DIR__ . '/../config/common-local.php'),
    require(__DIR__ . '/../config/web.php'),
    require(__DIR__ . '/../config/web-local.php')
);

В итоге мы можем делиться общими файлами и хранить персональные настройки в локальных.

Неразделённой осталась только тестовая конфигурация:

tests
└── codeception
    └── config
        ├── acceptance.php
        ├── functional.php
        ├── unit.php
        └── config.php

Внутри файла config.php жёстко вписаны настройки подключения к базе данных:

return [
    ...
    'components' => [
        'db' => [
            'dsn' => 'mysql:host=localhost;dbname=yii2_basic_tests',
        ],
        'mailer' => [
            'useFileTransport' => true,
        ],
        'urlManager' => [
            'showScriptName' => true,
        ],
    ],
];

У каждого разработчика база будет своя. Нам нужно как-то вынести эти персональные настройки из под надзора системы контроля версий.

Для этого аналогично обнулим здесь параметр dsn:

return [
    ...
    'components' => [
        'db' => [
            'dsn' => '',
        ],
        'mailer' => [
            'useFileTransport' => true,
        ],
        'urlManager' => [
            'showScriptName' => true,
        ],
    ],
];

И рядом создадим файл config-local.php с кодом:

return [
    'components' => [
        'db' => [
            'dsn' => 'mysql:host=localhost;dbname=yii2_basic_tests',
        ],
    ],
];

и файл .gitignore:

*-local.php

После этого структура конфигурации у нас будет такой:

tests
└── codeception
    └── config
        ├── .gitignore
        ├── acceptance.php
        ├── functional.php
        ├── unit.php
        ├── config.php
        └── config-local.php

Осталось изменить все файлы, которые пока используют старую конфигурацию. У нас это файлы:

tests
└── codeception
    ├── bin
    │   └── yii
    └── config
        ├── acceptance.php
        ├── functional.php
        └── unit.php

Тот же файл acceptance.php имеет вид:

return yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../../../config/web.php'),
    require(__DIR__ . '/config.php'),
    [
 
    ]
);

Исправляем его код на такой:

return yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../../../config/common.php'),
    require(__DIR__ . '/../../../config/common-local.php'),
    require(__DIR__ . '/../../../config/web.php'),
    require(__DIR__ . '/../../../config/web-local.php'),
    require(__DIR__ . '/config.php'),
    require(__DIR__ . '/config-local.php'),
    [
 
    ]
);

Аналогично дополняем списки слияния в остальных файлах functional.php, unit.php и bin/yii.

Теперь нам нужно куда-то поместить образец файла config-local.php, чтобы другие разработчики знали, как его заполнять. А ещё лучше сделать его автоматически появляющимся здесь при инициализации приложения. Как мы помним, мы скопировали из yii2-app-advanced скрипт init с папкой environments. Так что можно поместить заготовку нашего файла туда в подпапку окружения dev:

environments
├── dev
│   ├── config
│   │   ├── common-local.php
│   │   ├── console-local.php
│   │   ├── params-local.php
│   │   └── web-local.php
│   ├── tests
│   │   └── codeception
│   │       └── config
│   │           └── config-local.php
│   ├── web
│   │   ├── index.php
│   │   └── index-test.php
│   └── yii
├── index.php
└── prod
    ├── config
    │   ├── common-local.php
    │   ├── console-local.php
    │   ├── params-local.php
    │   └── web-local.php
    ├── web
    │   └── index.php
    └── yii

В prod-окружении тесты не запускают, так что туда эти данные помещать не нужно.

В итоге, при запуске команды:

php init

при установке приложения эти заготовки файлов автоматически скопируются на их исходные места.

Проверим правильность установки. Для этого можно создать тестовую базу, прописать её имя в своём рабочем tests/codeception/config/config-local.php и попробовтаь запустить миграции для того самого скрипта yii:

php tests/codeception/bin/yii migrate

Если настройки базы указаны верно, то миграция создаст в ней такую же таблицу user и свою вспомогательную migration.

Именно так из двух скриптов yii и tests/codeception/bin/yii можно автоматически обновлять рабочую и тестовую базы одними и теми же миграциями нашего приложения.

Перейдём к настройкам самого Codeception.

Настройка запуска и фикстуры

Изначально нас интересуют эти файлы:

tests
└── codeception
    ├── acceptance
    │   └── _bootstrap.php
    ├── functional
    │   └── _bootstrap.php
    └── unit
        └── _bootstrap.php

Файлы acceptance/_bootstrap.php и functional/_bootstrap.php имеют внутри себя код запуска приложения с соответствующим конфигурационным файлом:

new yii\web\Application(require(dirname(__DIR__) . '/config/acceptance.php'));

и:

new yii\web\Application(require(dirname(__DIR__) . '/config/functional.php'));

Внутри же третьего unit/_bootstrap.php такого кода нет, так как создание и завершение приложения находятся в методах setUp() и tearDown() самого класса yii\codeception\TestCase, от которого будут наследоваться классы наших тестов из папки unit.

Далее рассмотрим именно настройки Codeception:

tests
├── codeception
│   ├── acceptance.suite.yml
│   ├── functional.suite.yml
│   └── unit.suite.yml
└── codeception.yml

В общем файле codeception.yml настроим анализатор покрытия (test coverage) нашего кода. Включаем только папки с кодом и исключаем всё лишнее. Другие секции нас вполне устраивают и мы будем запускать тесты по адресу http://localhost:8080, поэтому всё остальное оставляем как есть:

actor: Tester
coverage:
    enabled: true
    whitelist:
        include:
            - ../components/*
            - ../mail/*
            - ../modules/*
            - ../views/*
        exclude:
            - ../modules/admin/messages/*
            - ../modules/main/messages/*
            - ../modules/user/messages/*
    blacklist:
        include:
            - ../assets/*
            - ../config/*
            - ../runtime/*
            - ../environments/*
            - ../messages/*
            - ../vendor/*
            - ../web/*
            - ../tests/*
paths:
    tests: codeception
    log: codeception/_output
    data: codeception/_data
    helpers: codeception/_support
settings:
    bootstrap: _bootstrap.php
    suite_class: \PHPUnit_Framework_TestSuite
    memory_limit: 1024M
    log: true
    colors: true
config:
    # the entry script URL (with host info) for functional and acceptance tests
    # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
    test_entry_url: http://localhost:8080/index-test.php

Файл unit.suite.yml весьма скромен со своей единственной строкой:

class_name: UnitTester

Оставляем его тоже.

Перед каждым запуском нужно заполнять базу тестовыми данными. Для этих вещей можно подключить и настроить модуль Db для импорта дампа, но фреймворк предоставляет работу с тестовыми данными в виде простых PHP-массивов.

Для модульных и интеграционных тестов расширение yii2-codeception поддерживает фикстуры «из коробки». Другое дело – - функциональные и приёмочные. Для поддержки в них миграций можно пойти путём написания собственного плагина для Codeception. Мы, как и говорили на вебинаре, возьмём готовый FixtureHelper из advanced-приложения, поместим его в папку tests/codeception/_support и поменяем лишь пути и пространство имён:

namespace tests\codeception\_support;
 
// use tests\codeception\fixtures\UserFixture;
use Codeception\Module;
use yii\test\FixtureTrait;
use yii\test\InitDbFixture;
 
class FixtureHelper extends Module
{
    use FixtureTrait {
        loadFixtures as protected;
        fixtures as protected;
        globalFixtures as protected;
        unloadFixtures as protected;
        getFixtures as protected;
        getFixture as protected;
    }
 
    public function _beforeSuite($settings = [])
    {
        $this->loadFixtures();
    }
 
    public function _afterSuite()
    {
        $this->unloadFixtures();
    }
 
    public function globalFixtures()
    {
        return [
            InitDbFixture::className(),
        ];
    }
 
    public function fixtures()
    {
        return [
            //'user' => [
            //    'class' => UserFixture::className(),
            //    'dataFile' => '@tests/codeception/fixtures/data/user.php',
            //],
        ];
    }
}

Фикстуру UserFixture мы пока закомментировали. Скоро мы её добавим свою.

Теперь подключим этот модуль для наших функциональных и приёмочных тестов. Откроем functional.suite.yml и добавим его в массив активных модулей:

class_name: FunctionalTester
modules:
    enabled:
        - Filesystem
        - Yii2
        - tests\codeception\_support\FixtureHelper
    config:
        Yii2:
            configFile: 'codeception/config/functional.php'

Аналогично добавим его в acceptance.suite.yml. Заодно и вместо curl-эмулятора браузера PhpBrowser подключим Facebook WebDriver, предназначенный для работы с Selenium Server, и настроим его на запуск браузера Firefox:

class_name: AcceptanceTester
modules:
    enabled:
        - WebDriver
        - tests\codeception\_support\FixtureHelper
    config:
        WebDriver:
            url: http://localhost:8080
            browser: firefox
            restart: true
            window_size: 1024x768

На этом конфигурирование завершено.

Установка Codeception и Selenium

Просто сконфигурировать нашу папку недостаточно. Нужно установить сам фреймворк Codeception и для запуска приёмочных тестов установить Selenium Standalone Server, который будет запускать для нас браузер Firefox (он тоже должен быть установлен).

Сейчас активно разрабатывается Codeception 3.0 и доводится 2.1, но Yii2 расчитан на стабильный релиз 2.0. Так что устанавливать мы будем именно 2.0. Подсмотрим подсказку в файле tests/README.md нашего Yii2-приложения и установим глобально:

composer global require "codeception/codeception=2.0.*"
composer global require "codeception/specify=*"
composer global require "codeception/verify=*"

Потом посмотрим, где у нас хранит настройки Composer:

composer global status

Это нам выведет его системную папку. Там тоже создаётся такая же папка vendor и пакеты ставятся в неё. Именно туда устанавливается тот самый composer-asset-plugin, который мы ставим перед установкой Yii2 командой:

composer global require "fxp/composer-asset-plugin:~1.1.1"

Да-да. Мы можем ставить пакеты в глобальный vendor, и автозагрузка классов при этом будет работать как будто мы поставили пакет к себе.

Попробуем запустить команду codecept. В Linux путь до неё такой:

~/.composer/vendor/bin/codecept

Мы должны увидеть приветствие:

Codeception version 2.0.16

Желательно добавить путь папки bin в переменную PATH нашей системы, чтобы можно было запускать скрипт по имени без указания пути:

codecept

Второй на очереди – Selenium-сервер. Это .jar-приложение. Для него, соответственно, нужно установить JRE от Java (но если у вас уже установлен редактор PhpStorm, то Java у вас уже есть).

Запускается стандартно:

java -jar selenium-standalone-server-xxx.jar

Но чтобы не возиться со скачиванием файла вручную и созданием ярлыка для его запуска лучше просто установим сам сервер и запускающий скрипт через Composer:

composer global require se/selenium-server-standalone

и, если у вас путь к bin уже прописан в PATH, будем запускать в консоли просто по этому же имени:

selenium-server-standalone

Написание своих тестов

В папке tests/codeception/fixtures создадим класс UserFixture с таким кодом:

namespace tests\codeception\fixtures;
 
use yii\test\ActiveFixture;
 
/**
 * User fixture
 */
class UserFixture extends ActiveFixture
{
    public $modelClass = 'app\modules\user\models\User';
    public $dataFile = '@tests/codeception/fixtures/data/user.php';
}

и в подпапке data создадим файл user.php с нашими наборами:

return [
    [
        'username' => 'admin',
        'auth_key' => 'eckb2DLY9uv6r1hM6D73eoHPvv6BfnXc',
        'password_hash' => '$2y$13$D8areN6YSJh.fmR.Ww/sWOJ8EXRxNS9c7u7ubIrVozomTR8MY0PbO',
        'password_reset_token' => null,
        'email_confirm_token' => null,
        'created_at' => '1439635619',
        'updated_at' => '1439635619',
        'email' => 'admin@example.com',
        'status' => 1,
    ],
    [
        'username' => 'reset',
        'auth_key' => 'eckb2DLY9uv6r1hM6D73eoHPvv6BfnXc',
        'password_hash' => '$2y$13$D8areN6YSJh.fmR.Ww/sWOJ8EXRxNS9c7u7ubIrVozomTR8MY0PbO',
        'password_reset_token' => null,
        'email_confirm_token' => null,
        'created_at' => '1439635619',
        'updated_at' => '1439635619',
        'email' => 'reset@example.com',
        'status' => 1,
    ],
];

и подключим эту фикстуру в нашем модуле FixtureHelper:

namespace tests\codeception\fixtures;
 
use tests\codeception\fixtures\UserFixture;
...
 
class FixtureHelper extends Module
{
    ...
 
    public function fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => '@tests/codeception/fixtures/data/user.php',
            ],
        ];
    }
}

Теперь возьмем уже имеющиеся стандартные тесты, идущие в комплекте yii2-app-basic, переделаем их под свои нужды и допишем свои.

Хорошая практика – раскладывать проверяющие классы по таким же папкам, где лежат оригинальные. Поэтому переместим всё как нам нужно:

tests
├── codeception
│   ├── bin
│   ├── config
│   ├── fixtures
│   │   ├── data
│   │   │   └── user.php
│   │   └── UserFixture.php
│   ├── _pages
│   │   ├── admin
│   │   │   └── users
│   │   │       ├── UserCreatePage.php
│   │   │       └── UserUpdatePage.php
│   │   ├── main
│   │   │   └── ContactPage.php
│   │   └── user
│   │       ├── EmailConfirmPage.php
│   │       ├── LoginPage.php
│   │       ├── PasswordResetPage.php
│   │       ├── PasswordResetRequestPage.php
│   │       ├── profile
│   │       │   ├── PasswordChangePage.php
│   │       │   └── UpdatePage.php
│   │       └── SignupPage.php
│   ├── _support
│   │   └── FixtureHelper.php
│   │
│   ├── acceptance
│   │   ├── AcceptanceCest.php
│   │   ├── AcceptanceTester.php
│   │   ├── admin
│   │   │   └── HomeCest.php
│   │   ├── _bootstrap.php
│   │   ├── main
│   │   │   ├── ContactCest.php
│   │   │   └── HomeCest.php
│   │   └── user
│   │       ├── LoginCest.php
│   │       ├── PasswordResetCest.php
│   │       ├── profile
│   │       │   ├── HomeCest.php
│   │       │   └── PasswordChangeCest.php
│   │       └── SignupCest.php
│   │
│   ├── functional
│   │   ├── admin
│   │   │   ├── HomeCest.php
│   │   │   └── UsersCest.php
│   │   ├── _bootstrap.php
│   │   ├── FunctionalCest.php
│   │   ├── FunctionalTester.php
│   │   ├── main
│   │   │   ├── ContactCest.php
│   │   │   └── HomeCest.php
│   │   └── user
│   │       ├── LoginCest.php
│   │       ├── PasswordResetCest.php
│   │       ├── profile
│   │       │   ├── HomeCest.php
│   │       │   ├── PasswordChangeCest.php
│   │       │   └── ProfileUpdateCest.php
│   │       └── SignupCest.php
│   │
│   ├── unit
│   │   ├── _bootstrap.php
│   │   ├── fixtures
│   │   │   └── data
│   │   │       ├── user-email-confirm.php
│   │   │       └── user-password-reset.php
│   │   ├── modules
│   │   │   ├── admin
│   │   │   │   └── models
│   │   │   │       └── UserTest.php
│   │   │   ├── main
│   │   │   │   └── forms
│   │   │   │       └── ContactFormTest.php
│   │   │   └── user
│   │   │       ├── forms
│   │   │       │   ├── EmailConfirmFormTest.php
│   │   │       │   ├── LoginFormTest.php
│   │   │       │   ├── PasswordChangeFormTest.php
│   │   │       │   ├── PasswordResetFormTest.php
│   │   │       │   ├── PasswordResetRequestFormTest.php
│   │   │       │   ├── ProfileUpdateFormTest.php
│   │   │       │   └── SignupFormTest.php
│   │   │       └── models
│   │   │           └── UserTest.php
│   │   └── UnitTester.php
│   │
│   ├── _bootstrap.php
│   ├── acceptance.suite.yml
│   ├── functional.suite.yml
│   └── unit.suite.yml
│
├── codeception.yml
└── README.md

Исходный код каждого файла можно посмотреть в исходниках на GitHub.

Нам практически всегда нужно будет логиниться на сайт для проверки панели управления и личного кабинета пользователя, поэтому для удобства мы создали базовые классы AcceptanceCest и FunctionalCest.

Приёмочные тесты более высокоуровневые и не всегда нужны при наличии функциональных. Приёмочные полезны для проверки JavaScript и CSS. Клиентских скриптов у нас пока нет, так что пока их можно просто продублировать из функциональных с добавлением пауз для Selenium:

if (method_exists($I, 'wait')) {
    $I->wait(1); // only for selenium
}

И попробуем их запустить.

Запуск тестов

После установки переходим в папку tests и, если это наш новый проект, генерируем классы тестеров:

cd tests
codecept build

Дальше пробуем запустить сначала модульные и интеграционные из папки unit:

codecept run unit

Успешный результат выглядит так:

Codeception PHP Testing Framework v2.0.16
Powered by PHPUnit 4.7.7 by Sebastian Bergmann and contributors.

Unit Tests (26) -------------------------------------------------------------------------------
Test validate empty new password (UserTest::testValidateEmptyNewPassword)                   Ok
Test validate wrong new password (UserTest::testValidateWrongNewPassword)                   Ok
...
Test correct signup (SignupFormTest::testCorrectSignup)                                     Ok
Test not correct signup (SignupFormTest::testNotCorrectSignup)                              Ok
-----------------------------------------------------------------------------------------------

Time: 7.37 seconds, Memory: 39.75Mb

OK (27 tests, 74 assertions)

И запустим функциональные:

codecept run functional

С виду здесь нет никакой разницы, но Codeception вместо создания объектов и вызова методов будет эмулировать запуск приложения, расставляя $_GET, $_POST и $_SERVER так, как будто мы открываем страницы и отправляем POST-запросы:

Codeception PHP Testing Framework v2.0.16
Powered by PHPUnit 4.7.7 by Sebastian Bergmann and contributors.

Functional Tests (17) -------------------------------------------------------------------------
Ensure that admin home page works (HomeCest::testAccess)                                    Ok
Ensure that admin home page works (HomeCest::testHomePage)                                  Ok
...
Ensure that password change works (PasswordChangeCest::testPasswordChange)                  Ok
Ensure that profile update form works (ProfileUpdateCest::testProfileUpdate)                Ok
-----------------------------------------------------------------------------------------------

Time: 15.36 seconds, Memory: 95.25Mb

OK (17 tests, 129 assertions)

Для запуска реальных приёмочных тестов нужно поднять тестовый сайт по адресу http://localhost:8080/index-test.php. Для этого по подсказке той же инструкции в tests/README.md откроем ещё одну консоль и в ней запустим PHP-сервер из папки web:

cd web
php -S localhost:8080

Он нам сообщит о своей готовности:

PHP Development Server started at Tue Feb  2 21:14:00 2016
Listening on http://localhost:8080
Press Ctrl-C to quit.

Откроем ещё одно окно консоли и запустим Selenium:

selenium-server-standalone

Увидим его приветствие:

21:16:51.796 INFO - Launching a standalone Selenium Server
21:16:51.977 INFO - Selenium Server is up and running

И, запустив в первоначальной консоли приёмочные из папки tests:

codecept run acceptance

мы должны увидеть на экране снова и снова открывающийся браузер, в котором будут сами открываться страницы, заполняться поля форм и нажиматься кнопки. При этом в консоли будет выводиться такой же как у функциональных результат:

Codeception PHP Testing Framework v2.0.16
Powered by PHPUnit 4.7.7 by Sebastian Bergmann and contributors.

Acceptance Tests (12) -------------------------------------------------------------------------
Ensure that access control works (HomeCest::testAccess)                                     Ok
Ensure that admin home page works (HomeCest::testHomePage)                                  Ok
...
Ensure that profile home page works (HomeCest::testHomePage)                                Ok
Ensure that login works (PasswordChangeCest::testPasswordChange)                            Ok
-----------------------------------------------------------------------------------------------

Time: 1.72 minutes, Memory: 19.00Mb

OK (12 tests, 80 assertions)

Если какой-либо тест завершится с ошибкой можно будет посмотреть отчёт в папке tests/codeception/_output.

Анализ покрытия кода тестами

Этот анализ позволяет посмотеть, какие участки кода отработали в ходе тестирования. В Ubuntu и Debian устанавливаем стандартно:

aptitude install php5-xdebug

В Windows нужно в php.ini добавить

[xdebug]
zend_extension_ts=C:/php/ext/php_xdebug.dll

Но если в ваших тестах используется $this->specify(...), то будет постоянно вываливаться ошибка из-за ограничений уровня вложенности. Для решения проблемы нужно открыть php.ini или /etc/php/conf.d/xdebug.ini и дописать:

xdebug.max_nesting_level = 1000

С установкой всё. Попробуем запустить всё снова, но с анализом покрытия:

codecept run unit,functional --coverage-html

И после завершения посмотрим _output/coverage/index.html:

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

Следующая часть: Организация переносимых модулей

Другие статьи

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

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

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

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

Комментарии

 

bobpps

Большущее спасибо! Вы, как всегда, на высоте!

Ответить

 

Олег Мусатов

Неделю, как взялся за чтение Вашего блога. Понял в yii2 больше, чем за месяц до этого. Очень качественная и подробная информация.

Жду не дождусь статьи по подключению rbac.

Ответить

 

Алексей

И еще, что бы работал coverage, в php.ini должен быть включен php_xdebug.dll, иначе будет ругаться на отсутствие драйвера - No code coverage driver available

Ответить

 

Дмитрий Елисеев

Спасибо! Добавил в статью.

Ответить

 

Grey

Спасибо за очередную интересную статью, Очень интересует, как можно сделать правильно авторизацию, через социальные сети. В рамках данного проекта.

Ответить

 

Дмитрий Елисеев

Есть пример на эту тему в самом расширении.

Ответить

 

Grey

Спасибо

Ответить

 

Кабаченко Виктор

Большое спасибо за вашу титаническую работу. Таких учебных материалов по тестированию php-проектов еще не было. Хочется попросить вас не оставлять эту тему и при развитии этого проекта обратить внимание на технологию тестирования javascript/ajax с помощью приемочных тестов, ну а также побольше внимания уделить mocks/stubs в модульном тестировании

Ответить

 

nikosid

Подскажите в чём беда. Я сделал всё по инструкции, но у меня ломаются тесты acceptance.
После долгих мучений, я скачал и установил себе голый yii2-app-advanced и с ним та же беда, не отрабатывают тесты для LoginPage например, а именно те, которые должны увидеть ошибку:

 Step  I see "Username cannot be blank.",".help-block"
 Fail  Element located either by name, CSS or XPath element with '.help-block' was not found.


и в _output в html и скриншоте вижу, что ошибка не выводилась.
При этом просто в браузере всё отрабатывает.

Ответить

 

nikosid

Забыл сказать, это только в случае с WebDriver.
Если запускать с PhpBrowser, то всё ок.

Ответить

 

Дмитрий Елисеев

Возможно не успевают появиться. Можно попробовать паузы в sleep(...) увеличить.

Ответить

 

Роман Пулиян

Привет, а как сделать, чтобы перед запуском теста база полностью очищалась от данных из предыдущих тестов? Само как-то должно произойти, или я руками должен очистку делать, например в setUp()?
setUp() и tearDown() срабатывают перед и после каждого теста, верно?

Ответить

 

Дмитрий Елисеев

Я вручную вычищаю. Либо в setUp(), либо в конце теста. А вообще стараюсь делать так, чтобы тесты друг другу не мешали.

Ответить

 

nikosid

А что за файл генерируется при каждом тестировании?

tests/codeception/common/_support/_generated/UnitTesterActions.php

Содерживое его не меняется, а только [STAMP]
В общем-то вот такое содержимое

<?php  //[STAMP] 3855f4aae2d90a3240564b5fb541a01d
namespace tests\codeception\common\_generated;

trait UnitTesterActions
{
    /**
     * @return \Codeception\Scenario
     */
    abstract protected function getScenario();   
}
Ответить

 

Дмитрий Елисеев

Раньше в 2.0 для подключения модулей перегенерировался целиком весь AcceptanceTester. Это не позволяло добавлять туда свои методы вроде $I->login('name', 'password'), так как при перегенерации все наши методы перезатирались.

В 2.1 каждый тестер разбит на две части: сам класс и его автогенерируемый трейт. Теперь в класс можно помещать свои методы, не боясь их перезатирания:

class AcceptanceTester extends \Codeception\Actor
{
    use _generated\AcceptanceTesterActions;

    public function login($name, $password)
    {
        $I = $this;
        $I->amOnPage('/login');
        $I->submitForm('#loginForm', [
            'login' => $name, 
            'password' => $password
        ]);
        $I->see($name, '.navbar');
    } 
}

Пример из http://codeception.com/docs/06-ReusingTestCode.

Ответить

 

nikosid

Как с ним правильно поступить? Заигнорить в гите?

Ответить

 

Дмитрий Елисеев

Можно и заигнорить.

Ответить

 

Sergey

Дмитрий, такой вопрос по тестированию.
Исходные данные:
есть RBAC на основе DB manager. есть пользователи с разными ролями(у ролей свои разрешения и т.д.).

Вопросы:
как можно тестировать настройки доступа по RBAC, если пользователям нужно выставить права/роли при регистрации/добавлении?
т.е. к примеру хочу использовать фикстуры, но тогда как мне одновременно с определением пользователей задать привязки ролей (через $this->users[0] - можно ведь получить только данные фикстуры, но не ID вставленной записи - или ошибаюсь?)?
или если я задам в основной БД все роли и заранее создам тестовых пользователей, то смогу ли использовать фикстуры (не перепишется ли эта информация)? имею ввиду фикстуры как для таблицу пользователей, так и для других таблиц?
как я понимаю каждая фикстура отвечает за свою таблицу, верно?

заранее спасибо!

Ответить

 

Дмитрий Елисеев

> если я задам в основной БД все роли и заранее создам тестовых пользователей, то смогу ли использовать фикстуры (не перепишется ли эта информация)

Именно этих тестовых пользователей с ролями и создавайте в фикстурах. Они для этого и предназначены. Тестирование производится в тестовой БД, а не в основной. Все данные из фикстур пойдут туда.

> как я понимаю каждая фикстура отвечает за свою таблицу, верно?

Да. И к тесту регистрации подключайте все нужные для него UserFixture, AuthItemFixture, AuthAssignmentFixture и т. п.

> можно ведь получить только данные фикстуры, но не ID вставленной записи - или ошибаюсь?

Для заранее создаваемых в фикстурах пропишите id:

[
    'id' => 1,
    'username' => 'admin',
    'email' => 'admin@example.com',
],

и получайте через $this->users[0]['id'].

А вновь созданных после регистрации (которых нет в $this->users) можно просто найти в базе по имени, под которым их регистрировали:

$user = User::findOne('name' => 'new_name');

и у них брать $user->id;

Ответить

 

Анатолий Белов

Все супер, труд титанический. Жаль, что не работает. Вроде всё по инструкции, ан нет.
Для меня проблема в том, что я не могу отличить обзорный код от рабочего. Сперва пишут так, далее вы говорите, что теперь нужно так, переделываю, потом еще, потом еще, потом ничего не работает и очень сложно понять что. Возможно, лучше отделить то, что в итоге не будет внесено в проект спойлером. Сейчас нереально проследить нить изменений.
С первой статьи проекта, рабочего кода нет, лишь концепт. В голове каша. У меня другие версии компонентов и php, может по этому.
Смотрел около семи часов скринкаст по тестированию, теперь применяю миграции для тестов и тупик полный, ничего за эти ошибки нет в сети.

Ответить

 

Дмитрий Елисеев

Просто год назад я не знал, что и как буду переделывать сегодня.

Ответить

 

Анатолий Белов

Согласен, что теперь сделаешь... как есть. Я думал сперва, вы специально так сделали. Чтобы мы мозг включали. Но местами слишком запутано получается. Спасибо, что делитесь своим опытом разработки, это главное. Тяжело найти реальный опыт, к тому же, вы показываете множество вариантов подхода. Уже почти досмотрел, что у вас есть, буду ждать новых вебинаров и статей. Удачи.

Ответить

 

Александр

Круто, спасибо за статью! Только начала по ней настраивать свое приложение и тут же вопрос возник. Дело в том, что в моем приложении есть 2 модуля (user, rbac), остальное пока в бекенде (categories, posts). Так вот миграции по модулю user лежат в модуле: modules/user/mogrations, остальные миграции по постам и категориям как обычно в console/migrations. При запуске php tests/codeception/bin/yii migrate конечно пытается создаться таблицы только post && category, хотя сначала должна быть таблица user (на нее ссылаются таблицы приложения), но о ней (о миграции создания таблицы user) тесты ничего не знают.
Дмитрий, как можно указать тестам очередность директорий миграций, с которых должна быть сборка?

Ответить

 

Дмитрий Елисеев

Подключите любое расширение вроде multipath-migrations и пропишите ему все пути с миграциями.

Ответить

 

Александр

О, спасибо, буду пробовать

Ответить

 

Андрей Русскин

Подскажите пожалуйста по UNIT тестированию.
Не могу настроить автозагрузку классов
Есть например модель User c с методом

public function fake(){
    return true;
}

для того чтобы протестировать этот метод надо руками инклюдить файл

__DIR__.'/../../models/User.php';

как автоматически их подключать?

и как запустить консольное приложения для тестирования экшенов консольного контроллера?

И не могу обратиться к БД
получаю ошибку TaskTest: User exception

PHP Fatal error:  Call to a member function getDb()

Что я делаю не так? :(

Ответить

 

Дмитрий Елисеев

В стандартных приложениях автозагрузка работает автоматически, если наследовать тесты от yii\codeception\TestCase.

А консольные контроллеры не тестировал.

Ответить

 

Андрей Русскин

да в том то и дело что наследуюсь от \yii\codeception\TestCase
и пытаюсь прогнать тест

public function testMe()
{
    $model = \app\models\User::find()->all();
    $this->assertEquals(3,count($model));
}

и получаю ошибку
MePHP Fatal error: Class 'app\models\User' not found in /var/www/test1/tests/unit/CronControllerTest.php

/var/www/test1/tests/_bootstrap.php содержит:

<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');

defined('YII_TEST_ENTRY_URL') or define('YII_TEST_ENTRY_URL', parse_url(\Codeception\Configuration::config()['config']['test_entry_url'], PHP_URL_PATH));
defined('YII_TEST_ENTRY_FILE') or define('YII_TEST_ENTRY_FILE', dirname(dirname(__DIR__)) . '/web/index-test.php');

require_once(__DIR__ . '/../vendor/autoload.php');
require_once(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

$_SERVER['SCRIPT_FILENAME'] = YII_TEST_ENTRY_FILE;
$_SERVER['SCRIPT_NAME'] = YII_TEST_ENTRY_URL;
$_SERVER['SERVER_NAME'] = parse_url(\Codeception\Configuration::config()['config']['test_entry_url'], PHP_URL_HOST);
$_SERVER['SERVER_PORT'] =  parse_url(\Codeception\Configuration::config()['config']['test_entry_url'], PHP_URL_PORT) ?: '80';

Yii::setAlias('@tests', dirname(__DIR__));
Yii::setAlias('@app', dirname(dirname(__DIR__)));

чтото уже запутался куда рыть

Ответить

 

Дмитрий Елисеев
Yii::setAlias('@tests', __DIR__);
Yii::setAlias('@app', dirname(__DIR__));
Ответить

 

Андрей Русскин

Спасибо :)
никогда не пользовался dirname() потому и лопухнулся :(

Ответить

 

Katrin Parfenova

Пишу юнит тест...

public function testProfileWrongData()
    {
        $model = new Profile([
            'lang_id' => -2,       // д.б integer
            'user_id' => -99,        //   д.б integer и ищет такого юзера в базе
            'name' => 2,              // д.б срока
        ]);
        expect('model is not valid', $model->validate())->false();
        expect('lang_id is incorrect', $model->errors)->hasKey('lang_id');
        expect('user_id is incorrect', $model->errors)->hasKey('user_id');
        expect('name_phrase is incorrect', $model->errors)->hasKey('name');
    } 

правила проверки

  public function rules()
    {
        return [
            [['lang_id', 'user_id', 'name'], 'required'],
            [['lang_id', 'user_id'], 'integer'],
            [['name'], 'string', 'max' => 50],
            [['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['user_id' => 'id']],
        ];
    }

так вот в error на lang_id = -2 в ошибки не падает...все типа хорошо, если ставлю там строку 'test' - то ошибка выдается, что д.б integer....почему не проверяет на отрицательность..где я туплю...

Ответить

 

Дмитрий Елисеев

Добавьте 'exist' валидатор для 'lang_id', если это связь.

Ответить

 

Олег Кузьменко

Ошибка, потому что integer вполне может быть отрицательным числом, с точки зрения типа данных - это правильно и ошибки в валидации нет.

Укажите параметр

'min' => 1

там, где написан тип данных (аналогично полю 'name' ниже).

Ответить

 

Роман

Пытаюсь провести миграцию тестовой базы

php tests/codeception/bin/yii migrate

В консоли валятся ошибки:

PHP Fatal error:  Uncaught exception 'yii\base\UnknownPropertyException' with message 'Setting unknown property: yii\console\ErrorHandler::errorAction' in Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Component.php:197
Stack trace:
#0 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\BaseYii.php(521): yii\base\Component->__set('errorAction', 'main/default/er...')
#1 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Object.php(105): yii\BaseYii::configure(Object(yii\console\ErrorHandler), Array)
#2 [internal function]: yii\base\Object->__construct(Array)
#3 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\di\Container.php(374): ReflectionClass->newInstanceArgs(Array)
#4 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\di\Container.php(153): yii\di\Container->build('yii\\console\\Err...', Array, Array)
#5 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\BaseYii.php(344): yii\di\Container->get('yii\\console\\Err...', Array, Array)
#6 Z:\Programs\Sites\yi in Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Component.php on line 197

На просторах нэта нарыл информацию, мол нужно вставить блок:

'components' => [
            'error' => [
                'errorAction' => 'main/default/error',
        ],

в config/console.php.

Вставлял - не работает.

Почему? Или вставляю не туда? Или вообще всё неправильно?

Ответить

 

Дмитрий Елисеев

Компонент errorHandler с errorAction должен быть только в config/web.php. В консоли и в common он не нужен.

Ответить

 

Роман

Переместил созданный config-local.php в envoronments/dev/tests/codeception/

После этого, выполнил

php tests/codeception/bin/yii migrate


В итоге в site/config появилась поддиректоря tests/codeception/config-local.php с файлом.

????
Так должно быть для паблик проектов?

Но, самая главная проблема - это то, что не работает мигация в тестовую БД.

Уже всё 10 раз пересмотрел.

Z:\Programs\Sites\yii2-basic-elisdn\www>php tests/codeception/bin/yii migrate
PHP Fatal error:  Uncaught exception 'yii\base\UnknownPropertyException' with message 'Setting unknown property: yii\console\ErrorHandler::errorAction' in Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Component.php:197
Stack trace:
#0 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\BaseYii.php(521): yii\base\Component->__set('errorAction', 'main/default/er...')

Вот, что я делаю не так?
Почему обработчик ругается на errorHandler ?

Ответить

 

Дмитрий Елисеев

> Переместил созданный config-local.php в envoronments/dev/tests/codeception/

Зачем?

> Но, самая главная проблема - это то, что не работает мигация в тестовую БД.

Настройки компонента errorHandler должны быть только в config/web.php.

Ответить

 

Роман

Пропустил 1 подкаталог.
Переместил config-local.php, созданный в site/tests/codeception/config в директорию environments/dev/config/tests/codeception.
Как указано Вами Дмитрий.
После этого выполнил init.

Настройки компонента errorHandler только в config/web.php. В блоке components.

Вот и непонимаю почему валятся эксепшены?
Что не так?

Ответить

 

Роман

Вообщем проблема с хандлерами возникла из-за того, что (опять же по инструкции выше) дополнил файл bin/yii с путями как для тестовых конфигов:

return yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../../../config/common.php'),
    require(__DIR__ . '/../../../config/common-local.php'),
    require(__DIR__ . '/../../../config/web.php'),
    require(__DIR__ . '/../../../config/web-local.php'),
    require(__DIR__ . '/config.php'),
    require(__DIR__ . '/config-local.php'),
    [
 
    ]
);

А нужно было так:

$config = yii\helpers\ArrayHelper::merge(
    require(YII_APP_BASE_PATH . '/config/common.php'),
    require(YII_APP_BASE_PATH . '/config/common-local.php'),
    require(YII_APP_BASE_PATH . '/config/console.php'),
    require(YII_APP_BASE_PATH . '/config/console-local.php'),
    require(__DIR__ . '/../config/config.php'),
    require(__DIR__ . '/../config/config-local.php')
);

И шторм ни разу не выругался....

Ответить

 

Роман

При попытке установить Composer выдаёт:

Your requirements could not be resolved to an installable set of packages.
...
Installation failed, reverting ./composer.json to its original content.

Так понимаю главный composer.json что лежит в C:\Users\SomeUser\AppData\Roaming\Composer\ у меня кривой?

Где брать оригинал?

Или я неверно рассуждаю?

Ответить

 

Дмитрий Елисеев

В ... как раз данные об ошибке. Если там про bower-asset/jquery, то забыли установить asset-plugin первой строчкой.

Ответить

 

Роман

Сейчас нет возможности просмотреть. Может быть ошибка связана с тем, что у меня уже был установлен Codeception 2.2.1 глобально?

Ответить

 

Роман

Вобщем так и есть, ошибки возникают из-за наличия установленной версии 2.1.*.

Удалил. Установил указанную.

Почему Шторм ругается на строку:

use Codeception\Module;

в FixtureHelper ?

Нэймспэйс Codeception найти не может. Да и я тоже.

Ответить

 

Дмитрий Елисеев

В PhpStorm зайти в File - Settings - Languages & Frameworks - PHP.

Там в Include path добавить папку C:\Users\SomeUser\AppData\Roaming\Composer\vendor

Ответить

 

Роман

Добрый вечер.

При попытке запустить:

codecept build

выскакивает ошибка:

                                             
  [Codeception\Exception\Configuration]       
  Configuration file could not be found.      
  Run `bootstrap` to initialize Codeception. 

На всякий случай выполнил команду
tests\codeception\bin>codecept bootstrap
Не помогает.

В чём может быть дело?

Ответить

 

Дмитрий Елисеев

Запускайте в папке tests, а не tests/codeception/bin.

Ответить

 

Александр

Подскажите пожалуйста, что делать с FOREIGN_KEY_CHECKS. Глобально в субд конечно работает соблюдение ссылочной целостности. Но я не понимаю, делал по примеру, таблица вроде у нас одна только, user, и в ней нет внешних ключей.

Если запускать функциональные тесты с анализом покрытия кода

codecept run functional --coverage-html

то после того как тесты прошли, появляется ошибка (без анализатора всё нормально)

  [yii\db\Exception]
  PDOStatement::execute(): MySQL server has gone away
  The SQL being executed was: SET FOREIGN_KEY_CHECKS = 0

  [yii\base\ErrorException]
  PDOStatement::execute(): MySQL server has gone away

и еще после того как проходят acceptance тесты, не могу посмотреть фэйлы и ошибки, тоже выскакивает

[yii\db\Exception]
  SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
  The SQL being executed was: SET FOREIGN_KEY_CHECKS = 0

  [PDOException]
  SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Ответить

 

Дмитрий Елисеев

MySQL server has gone away - значит MySQL отключился по таймауту.

Ответить

 

Роман

Добрый вечер.
Дмитрий, почему не могу обнаружить после завершения

codecept run unit,functional --coverage-html

файл _output/coverage/index.html

В _output даже папки coverage нет.

Где-то прописать нужно? Где?

Ответить

 

Дмитрий Елисеев

А на свежем yii2-app-basic появляется?

Ответить

 

Роман

Дмитрий, добрый день.
Прошу прощения за продолжение оборвавшегося диалога, небыло доступа к ПК.

Да, на свежем yii2-app-basic появляется.
Где я мог ошибиться?

Ответить

 

Дмитрий Елисеев

А в codeception.yml секцию coverage настроили?

Ответить

 

Роман

Да всё настроено.
Вообщем пропустил этот момент временно. Перешёл к другой главе. Некоторое время спустя. Попробовал протестить и всё появилось. Видимо с Гита скопировал часть кода, относящуюся к следующей главе.

P.S. давно Вас не было слышно...

Ответить

 

Юрий

Здравствуйте!

Подскажите, почему не проходит часть тестов. Запускаю так codecept run unit.

Вываливаются ошибки вида:

tests\codeception\unit\models\ContactFormTest::testContact | email should be send | examples index 0
TypeError: Argument 1 passed to PHPUnit_Framework_TestCase::onNotSuccessfulTest() must be an instance of Exception, instance of Error given, called in /home/username/.composer/vendor/phpunit/phpunit/src/Framework/TestCase.php on line 851
tests\codeception\unit\models\LoginFormTest::testLoginNoUser
TypeError: Argument 1 passed to PHPUnit_Framework_TestCase::onNotSuccessfulTest() must be an instance of Exception, instance of Error given, called in /home/username/.composer/vendor/phpunit/phpunit/src/Framework/TestCase.php on line 851

Результат

FAILURES!
Tests: 32, Assertions: 47, Errors: 13, Failures: 2.
Ответить

 

Дмитрий Елисеев

Уберите опцию --no-colors отовсюду.

Ответить

 

Юрий

Скажите, пожалуйста, подробнее где эта опция задается? В ваших статьях ее не нашел.

Ответить

 

Дмитрий Елисеев

Если в PhpStorm по видео настраивали, то там. А если этого нет, то значит в другом дело. Видимо, что PHPUnit обновился сильно.

Ответить

 

Юрий

Нет, PhpStorm не настраивал по видео. Делал, все, как было в ваших постах SeoKeys.

Версии PHPUnit и Codeseption:

Codeception PHP Testing Framework v2.0.16
Powered by PHPUnit 4.7.7 by Sebastian Bergmann and contributors.

У меня, на этой машине стоит PHP 7, я думаю из-за этого возникает проблема. Пробовал, запускать тесты на проекте с гитхаба, были такие же проблемы.

Ответить

 

Дмитрий Елисеев

Может быть. А стандартные со стабильного yii2-app-basic запускаются?

Ответить

 

Юрий

Да, со стандартными, которые идут в yii2 basic, все ок.

Time: 871 ms, Memory: 10.00MB
OK (4 tests, 15 assertions)

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>