Как тестировать код, взаимодействующий с базой данных

Как тестировать код, взаимодействующий с базой данных

Если ваш код взаимодействует с базой данных, например, считывает данные из нее или сохраняет данные в нее, вам нужно настроить ваши тесты так, чтобы они это учитывали. Существует множество способов справиться с этим. В модульном тестировании вы можете создать макет Repository и использовать ее для возвращения ожидаемых объектов. В функциональном тесте вам может понадобиться подготовить тестовую БД с предопределёнными значениями, чтобы убедиться, что ваш тест всегда имеет одни и те же данные для работы.

Note

Если вы хотите тестировать ваши запросы напрямую, см. How to Test Doctrine Repositories.

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

Если вы хотите протестировать код, который зависит от хранилища Doctrine в изоляции, вам понадобится макетировать Repository. Обычно вы внедряете EntityManager в ваш класс и используете его для получения хранилища. Это немного усложняет вещи, так как вам понадобится делать макеты как EntityManager, так и вашего класса хранилища.

Tip

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

Представьте, что класс, который вы хотите протестировать, выглядит так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/AppBundle/Salary/SalaryCalculator.php
namespace AppBundle\Salary;

use Doctrine\ORM\EntityManagerInterface;

class SalaryCalculator
{
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function calculateTotalSalary($id)
    {
        $employeeRepository = $this->entityManager
            ->getRepository('AppBundle:Employee');
        $employee = $employeeRepository->find($id);

        return $employee->getSalary() + $employee->getBonus();
    }
}

Так как EntityManagerInterface внедряется в класс через конструктор, то очень легко передать объект-макет в пределах теста:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// tests/AppBundle/Salary/SalaryCalculatorTest.php
namespace Tests\AppBundle\Salary;

use AppBundle\Entity\Employee;
use AppBundle\Salary\SalaryCalculator;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;

class SalaryCalculatorTest extends TestCase
{
    public function testCalculateTotalSalary()
    {
        // Вначале, макетируйте объект, который будет использован в тесте
        $employee = $this->createMock(Employee::class);
        // используйте getMock() на PHPUnit 5.3 или ниже
        // $employee = $this->getMock(Employee::class);
        $employee->expects($this->once())
            ->method('getSalary')
            ->will($this->returnValue(1000));
        $employee->expects($this->once())
            ->method('getBonus')
            ->will($this->returnValue(1100));

        // Теперь, создайте макет хранилища так, чтобы он возвращал макет служащего
        $employeeRepository = $this
            ->getMockBuilder(EntityManagerInterface::class)
            ->disableOriginalConstructor()
            ->getMock();
        $employeeRepository->expects($this->once())
            ->method('find')
            ->will($this->returnValue($employee));

        // В конце, создайте макет EntityManager так, чтобы он возвращал макет хранилища
        $entityManager = $this
            ->getMockBuilder(EntityManagerInterface::class)
            ->disableOriginalConstructor()
            ->getMock();
        $entityManager->expects($this->once())
            ->method('getRepository')
            ->will($this->returnValue($employeeRepository));

        $salaryCalculator = new SalaryCalculator($entityManager);
        $this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1));
    }
}

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

Изменение настроек БД для функциональных тестов

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

Чтобы сделать это, вы можете указать конфигурацию БД, которая переопределяет конфигурацию по умолчанию:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/config_test.yml
    doctrine:
        # ...
        dbal:
            host:     localhost
            dbname:   testdb
            user:     testdb
            password: testdb
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- app/config/config_test.xml -->
    <doctrine:config>
        <doctrine:dbal
            host="localhost"
            dbname="testdb"
            user="testdb"
            password="testdb"
        />
    </doctrine:config>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // app/config/config_test.php
    $container->loadFromExtension('doctrine', array(
        'dbal' => array(
            'host'     => 'localhost',
            'dbname'   => 'testdb',
            'user'     => 'testdb',
            'password' => 'testdb',
        ),
    ));
    

Убедитесь в том, что ваша база данных работает на локальном хосте и имеет установленную определённую БД и учётные данные пользователя.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.