Разработка, HowTo

Часть 8: DBUnit (Тестирование ПО)

dbОглавление

Продолжаем серию статей Тестирование ПО, которая посвящена разработке ПО с применением методологии TDD.

В этой части будет рассматривать полезное дополнение к PHPUnit под названием DBUnit. Оно позволяет тестировать базу данных.

На предыдущем занятии мы тестировали форму аутентификации (не путайте процесс аутентификации и авторизации!). Для того чтобы проверить кейс входа существующего пользователя нам надо было создать запись об этом пользователе в бд. Делали мы это в методах. которые выполняются перез запуском тестов.

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

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

Установка и настройка

Проще всего установить DBUnit через менеджер пакетов composer. Для этого в каталоге /var/www/ виртуальной машины вводим команду добавления пакета.

composer require --dev phpunit/dbunit ^2

Этот код добавит последнюю версию пакета в раздел require-dev файла composer,json. Затем поставить его и обновит composer.lock. Конечно же вам потребуется закоммитить измененные файлы в систему контроля версий.

Почему мы используем версию 2.x.x, а не 3.x.x? Потому что некоторые модули из шаблона advanced-template еще не адаптированы под новую версию пакета на момент написания этого текста.

Теперь доработаем конфигурацию PHPUnit (файл environments/dev/phpunit.xml) добавив в него строки, которые содержат конфигурацию подключения к бд.

<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.7/phpunit.xsd"
bootstrap="common/tests/_bootstrap.php">
    <!-- старый код оставляем без изменений -->
    <php>
        <var name="DB_DSN" value="mysql:dbname=tdd_tests;host=127.0.0.1" />
        <var name="DB_USER" value="tdd" />
        <var name="DB_PASSWD" value="tdd" />
        <var name="DB_DBNAME" value="tdd_tests" />
    </php>
</phpunit>

Мы добавили секцию php содержащую переменные доступные при запуске тестов в супермассиве $GLOBALS. DBUnit не использует конфигурацию yii. Можно задействовать ее в процессе работы и это будет правильно. Но в общем случае конфигурацию подключения к базе для тестов хранится отдельно. Чуть позже мы увидим как объединить конфигурацию Yii и DBUnit.

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

Тестовая база

Предварительно потребуется сформировать набор данных, который будет составлять основу для наших тестов.

Форматов хранения существует несколько. Используем самый простой из них — flatXML.

common/tests/_data/database.xml

<?xml version="1.0" ?>
<dataset>
    <user id="1" username="test" email="test@test.test" auth_key="ssssssssssssssssssssssssssssssss"           password_hash="$2y$13$PP1EDCr7ujdhTxZT2DV96uM8e2rcdXHY1xAQINCIiB0gOck/VBwN6" />
    <user id="2" username="test1" email="test1@test.test"  auth_key="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"           password_hash="$2y$13$PP1EDCr7ujdhTxZT2DV96uM8e2rcdXHY1xAQINCIiB0gOck/VBwN6" />
</dataset>

Элементы набора dataset представляют из себя записи, где имя тега — это имя таблицы, куда будет записаны значения, а атрибуты — это значения текущей записи. Не забывайте, что пароли в базе хранятся в хешированом виде, поэтому потребуется каким-либо образом получить хеш пароля из Yii. Итого в таблице user у нас будет две записи — админ и пользователь. Пароли соответственно admin и user (в зашифрованном виде конечно же). Вы можете добавить сюда еще и те записи, которые использовали в своих тестовых сценариях. Это даже стоит сделать для того, чтобы не поломать уже существующие кейсы.

Тестовый сценарий

Редактируем код LoginFormTest и переводим его на использование DBUnit.

common/tests/unit/LoginFormTest.php

namespace common\tests\unit;

use common\models\LoginForm;
use PHPUnit_Extensions_Database_DataSet_IDataSet;
use PHPUnit_Extensions_Database_DB_IDatabaseConnection;
use yii\web\User;

class LoginFormTest extends \PHPUnit_Extensions_Database_TestCase
{
    protected const USER_EMAIL = 'test@test.test';

    protected static $_storedEntities = [
        'user' => null,
    ];

    /**
     * @var \PDO Подключение к бд.
     */
    protected static $pdo = null;

    /**
     * @var \PHPUnit_Extensions_Database_DB_IDatabaseConnection Подключение к базе
     */
    private $_conn = null;

    protected function getConnection()
    {
        if ($this->_conn === null) {
            if (self::$pdo == null) {
                self::$pdo = new \PDO($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']);
            }
            $this->_conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
        }

        return $this->_conn;
    }

    protected function getDataSet()
    {
        return $this->createFlatXMLDataSet(
            \Yii::$app->getBasePath()
            . DIRECTORY_SEPARATOR . '../common/tests/_data/database.xml'
        );
    }

    /**
     * Add default user to database,
     * Save original components from engine to temporary storage
     */
    public static function setUpBeforeClass()
    {
        foreach (static::$_storedEntities as $entity => $value) {
            static::$_storedEntities[$entity] = \Yii::$app->get($entity);
        }
    }

    /**
     * Restore original components after every test
     */
    protected function tearDown()
    {
        foreach (static::$_storedEntities as $entity => $value) {
            \Yii::$app->set($entity, $value);
        }
    }

    // все тесты остаются без изменений
}

Первое, что должно бросаться в глаза — это изменение иерархии наследования — теперь наш тест является прямым потомком PHPUnit_Extensions_Database_TestCase и к реализации становятся обязательны два метода:

  • getConnection() — получение подключения к тестовой бд
  • getDataSet() — загрузка в базу тестового набора данных

В первом на базе значений, которые были заданы в phpunit.xml мы получаем подключение к бд. PHPUnit использует интерфейс \PDO для работы с базой, поэтому не пытайтесь подключаться к базе через mysqli_connect. Второй метод — это источник данных. Посредством хитрых манипуляций внутри самого фреймворка он будет вызван попытке загрузить данные в бд.

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

public function testTestUserExists()
{
    $user = \common\models\User::findByEmail(self::USER_EMAIL);
    $this->assertNotEmpty($user);
}

public function testTest1UserExists()
{
    $user = \common\models\User::findByEmail('test1@test.test');
    $this->assertNotEmpty($user);
}

У нас уже был testOne — переименовываем его в testTestUserExists.

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

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

public function testTestUserLogin()
{
    $mock = $this->getMockBuilder(User::class)
        ->setMethods(['login'])
        ->disableOriginalConstructor()
        ->getMock();
    $mock->method('login')->withAnyParameters()->willReturn(true);
    \Yii::$app->set('user', $mock);
    $loginForm = new LoginForm();
    $loginForm->load(['LoginForm' => ['email' => static::USER_EMAIL, 'password' => static::USER_PASSWORD]]);
    $this->assertTrue($loginForm->login());
}

Чтобы пока не разбираться с авторизацией в консоли подменим метод \yii\web\User::login() своим, который возвращает true. Это необходимо потому что в консоли отсутствуют объекты Request и сессии. К ним мы еще вернемся.

Тест работает. Ошибок пока не находит. Отметим, что работать с базой при помощи DBUnit гораздо приятнее, нежели руками через setUp.

Внутреннее устройство

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

Конечно же стоит заметить, что поле id или любой другой первичный ключ (если он не является автогенерируемым должен присутствовать в датасете. И автогенерируемый ключ так же стоит включать в датасет — это обеспечит одинаковое состояние набора данных при запуске теста.

1. Очистка базы

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

2. Загрузка фикстур

PHPUnit проходит по всему набору данных из датасета и выполняет операцию INSERT чтобы вставить строки данных.

3–5. Запуск тестов, проверка и завершение (tearDown)

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

Тест может вызывать метод assertDataSetsEqual(), однако, эта функциональность опциональна.

Литература

Исходный код

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s