Введение в

модульное тестирование

Владимир Куприенко

Что такое модульные тесты?

Это процесс позволяющий проверить отдельные компоненты системы

Зачем они нужны?

  • Предотвращение регрессий в коде
  • Ускорение процесса исправления ошибок

Какой проект покрывать тестами?

Не стоит покрывать тестами "проекты-однодневки"

Тесты нужно писать для длительных проектов

(движки, open-source, монолитные системы)

Преимущества

  • Помогают строить архитектуру
  • Документируют код
  • Повышают доверие к коду

Codeception

и с чего начать

Что такое 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

codeception.yml

class_name: UnitTester # класс содержащий подключённые модули
bootstrap: false       # выключаем локальный для unit тестов bootstrap файл
modules:
    # включаем подключённые глобально модули
    enabled:
        - Asserts # Модуль с assert методами из PHPUnit
        - Db      # Модуль для работы с БД
        - Yii2    # Модуль для поддержки Yii2

tests/unit.suite.yml

<?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');

tests/_bootstrap.php

<?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' => [],
];

tests/_config/app.php

Генерируем тест-кейсы для классов

$ ./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

Введение в тестирование, Codeception и с чего начать

By Volodymyr Kupriienko

Введение в тестирование, Codeception и с чего начать

  • 1,541