Владимир Куприенко
Что такое модульные тесты?
Это процесс позволяющий проверить отдельные компоненты системы
Зачем они нужны?
Какой проект покрывать тестами?
Не стоит покрывать тестами "проекты-однодневки"
Тесты нужно писать для длительных проектов
(движки, open-source, монолитные системы)
Преимущества
Что такое Codeception?
Это full-stack фреймворк для тестирования построенный на PHPUnit который позволяет писать:
Как начать?
$ composer require --dev "codeception/codeception"
1. Устанавливаем codeception в проект
2. Инициализируем
$ ./vendor/bin/codecept bootstrap
3. Конфигурируем тестовое окружение
actor: Tester
paths:
tests: tests # путь к папке с тестами
log: tests/_output # путь к папке для записи логов
data: tests/_data # путь к папке с данными
support: tests/_support # путь к папке с файлами для тест-кейсов
settings:
bootstrap: _bootstrap.php # путь к bootstrap файлу
colors: true # цветная подсветка в консоли для удобства
memory_limit: 1024M # лимит оперативной памати для тестов
extensions:
enabled:
# расширение пишет подробности о упавших тестах в папку с логами
- Codeception\Extension\RunFailed
modules:
config:
# Модуль для поддержки Yii2
Yii2:
cleanup: false
configFile: 'tests/_config/app.php'
# Модуль для работы с БД
Db:
dsn: 'sqlite:tests/_output/test.db'
user: ''
password: ''
dump: 'tests/_data/dump.sql'
cleanup: true,
populate: true,
reconnect: false
class_name: UnitTester # класс содержащий подключённые модули
bootstrap: false # выключаем локальный для unit тестов bootstrap файл
modules:
# включаем подключённые глобально модули
enabled:
- Asserts # Модуль с assert методами из PHPUnit
- Db # Модуль для работы с БД
- Yii2 # Модуль для поддержки Yii2
<?php
error_reporting(-1);
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
defined('YII_ENABLE_ERROR_HANDLER')
or define('YII_ENABLE_ERROR_HANDLER', false);
defined('VENDOR_DIR')
or define(
'VENDOR_DIR',
__DIR__ . implode(DIRECTORY_SEPARATOR, ['', '..', 'vendor'])
);
require_once(VENDOR_DIR . DIRECTORY_SEPARATOR . 'autoload.php');
require_once(
VENDOR_DIR . implode(DIRECTORY_SEPARATOR, ['', 'yiisoft', 'yii2', 'Yii.php'])
);
Yii::setAlias('@tests', __DIR__);
Yii::setAlias('@vendor', VENDOR_DIR);
Yii::setAlias('@data', __DIR__ . DIRECTORY_SEPARATOR . '_data');
<?php
return [
'id' => 'test-app',
'class' => \yii\console\Application::class,
'basePath' => Yii::getAlias('@tests'),
'vendorPath' => Yii::getAlias('@vendor'),
'runtimePath' => Yii::getAlias('@tests/_output'),
'bootstrap' => [],
'components' => [
'db' => [
'class' => \yii\db\Connection::class,
'dsn' => 'sqlite:' . Yii::getAlias('@tests/_output/test.db'),
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
],
'params' => [],
];
Генерируем тест-кейсы для классов
$ ./vendor/bin/codecept generate:test unit DemoTest
<?php
class DemoTest extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
protected function _before() // setUp()
{
}
protected function _after() // tearDown()
{
}
// tests
public function testSomeFeature()
{
}
}
<?php
namespace app\models;
use yii\base\InvalidConfigException;
use yii\base\Model;
class Demo extends Model
{
private $_title;
public function init() {
if (empty($this->_title)) {
throw new InvalidConfigException();
}
}
public function setTitle($title) {
$this->_title = $title;
}
public function getTitle() {
return $this->_title;
}
}
Пишем тесты
class DemoTest extends \Codeception\Test\Unit
{
// tests
public function testCreateInstance()
{
$title = 'This is title!';
$model = new Demo(['title' => $title]);
// проверка типа данных
$this->assertInternalType(IsType::TYPE_STRING, $model->title);
// проверка на равенство
$this->assertEquals($title, $model->title);
}
/**
* @expectedException yii\base\InvalidConfigException
*/
public function testInit()
{
$this->expectException(InvalidConfigException::class);
new Demo();
}
}
$ ./vendor/bin/codecept build
Building Actor classes for suites: acceptance, functional, unit
-> AcceptanceTesterActions.php generated successfully. 0 methods added
\AcceptanceTester includes modules: PhpBrowser, \Helper\Acceptance
-> FunctionalTesterActions.php generated successfully. 0 methods added
\FunctionalTester includes modules: \Helper\Functional
-> UnitTesterActions.php generated successfully. 0 methods added
\UnitTester includes modules: Asserts, Db, Yii2
$ ./vendor/bin/codecept run unit
Codeception PHP Testing Framework v2.3.5
Powered by PHPUnit 6.2.4 by Sebastian Bergmann and contributors.
Unit Tests (2) -----------------------------------------------------------------------------------------------------------------------
✔ DemoTest: Create instance (0.00s)
✔ DemoTest: Init (0.00s)
--------------------------------------------------------------------------------------------------------------------------------------
Time: 111 ms, Memory: 10.00MB
OK (2 tests, 3 assertions)
assertEquals($expected, $actual)
assertTrue($condition) / assertFalse($condition)
assertNull($actual) / assertNotNull($actual)
assertInstanceOf($expectedClass, $actualObject)
assertInternalType($type, $actualObject)
assertRegExp($patter, $string) / assertNotRegExp($pattern, $string)
Распространённые assert-методы
Фикстуры
Компоненты которые хранят в себе определённое состояние системы
с помощью них можно загружать это состояние много раз
<?php
namespace app\models;
use yii\db\ActiveRecord;
class Article extends ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'article';
}
}
-- Table: article
DROP TABLE IF EXISTS article;
CREATE TABLE article (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title STRING,
description STRING,
full_text TEXT
);
Модель
<?php
// tests/fixtures/ArticleFixture.php
namespace tests\fixtures;
use yii\test\ActiveFixture;
class ArticleFixture extends ActiveFixture
{
public $modelClass = 'app\models\Article';
public $dataFile = '@data/article.php';
}
<?php
// tests/_data/article.php
return [
1 => [
'title' => 'Title #11',
'description' => 'Description #11',
'full_text' => 'Full text #11'
],
2 => [
'title' => 'Title #12',
'description' => 'Description #12',
'full_text' => 'Full text #12'
],
3 => [
'title' => 'Title #13',
'description' => 'Description #13',
'full_text' => 'Full text #13'
],
];
Фикстура для модели
<?php
use yii\test\FixtureTrait;
use tests\fixtures\ArticleFixture;
class ArticleTest extends \Codeception\Test\Unit
{
use FixtureTrait;
protected function _before()
{
$this->loadFixtures();
}
protected function _after()
{
$this->unloadFixtures();
}
public function fixtures()
{
return [
'article' => ArticleFixture::class,
];
}
}
Подготовка тест-кейса
<?php
class ArticleTest extends \Codeception\Test\Unit
{
// tests
public function testSave()
{
$title = 'Test title';
$description = 'Test description';
$fullText = 'Test full text';
$model = new Article([
'title' => $title,
'description' => $description,
'full_text' => $fullText,
]);
$model->save(false);
// Yii module
$this->tester->seeRecord(Article::class, [
'title' => $title,
'description' => $description,
'full_text' => $fullText,
]);
// DB module
$this->tester->seeInDatabase(Article::tableName(), [
'title' => $title,
'description' => $description,
'full_text' => $fullText,
]);
}
}
$ ./vendor/bin/codecept run unit ArticleTest
Codeception PHP Testing Framework v2.3.5
Powered by PHPUnit 6.2.4 by Sebastian Bergmann and contributors.
Unit Tests (1) -----------------------------------------------------------------------------------------------------------------------
✔ ArticleTest: Save (0.07s)
--------------------------------------------------------------------------------------------------------------------------------------
Time: 191 ms, Memory: 12.00MB
OK (1 test, 1 assertion)
Спасибо за внимание :)
With love