Как выполнить интеграционное тестирование с помощью dataProvider не создавая кучу экземпляров модели?

Хочу протестировать методы приложения. При этом это инеграционное тестирование, то есть прямая связь с БД. Например пользователей нужно проверить на "активность", на "роль/права", на "состоит ли в друзьях" и другие всякие методы. Для этого будет много разных методов тестирования. Но для каждого из них нужно много входных данных, провайдеры даннных. Сейчас я пишу что-то типа такого:

<?php

namespace tests\unit\services\user;

use app\models\User;
use app\services\User as UserService;

use Codeception\Test\Unit;

class UserTest extends Unit {
    
    /**
     * @var \UnitTester
     */
    protected $tester;  
    protected function _before() { }    
    protected function _after() { }
    
    /**
    * @dataProvider getUsersForActivity
    */
    public function testUserIsActive($user, $result) {  
        $userService = new UserService($user);
        self::assertEquals($userService->isActive(), true);
    }
    
    public function getUsersForActivity() {
        return [
            [User::findOne(['id' => 1]) => true],
            [User::findOne(['id' => 2]) => false],
            [User::findOne(['id' => 456]) => false],
            [User::findOne(['id' => 87]) => true],
            // куча других пользователей
        ];
    }
}

И вот таких провайдеров может быть десяток. В каждом из них модель обращается к БД, тянет данные и тестируется. Это слишком трудозатрадно для БД, учитывая, что это только пользователи, а могут быть ещё другие сотни тестов, которые ещё больше трудоёмкие.

Вопрос: как правильно делать интеграционное тестирование? Как правильно загрузить данные для прогона теста, чтобы это не загружать так БД? Например как-то создать кэшированный запрос и подставлять туда id из массива?


Ответы (1 шт):

Автор решения: Roman Konoval

Интеграционные тесты медленные, поэтому:

  1. их нужно использовать только для того, что нельзя протестировать в unit тестах.
  2. идти на определенные компромиссы

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

Важно понимать и четко себе ответить, что тестирует тест. Действительно ли интеграцию с БД. Или все же другую логику, которую можно протестировать в unit тесте, замокав вызов к БД. Это позволит существенно сократить количество тестов, которым действительно нужна БД.

Имеет смысл для тестирования использовать in-memory БД. Насколько я понимаю, то sqlite можно использовать в php в этом режиме. Настройте себе, чтобы при запуске интеграционных тестов использовалась именно inmemory БД.

Имеет смысл укрупнять тесты. То есть вместо тестирования конкретного метода, типа User.findOne тестируйте какой-то сценарий, в котором этот метод будет использован. Теряется независимость тестов и увеличивается гранулярность, поэтому нужно взвешивать, что важнее.

Что касается создания данных. Статические данные (типа словарей) имеет смысл создать заранее, и использовать уже готовую БД, чтоб во время выполнения тестов, не нужно было все вставлять по-новой.

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

→ Ссылка