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

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

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

Note

Если вы хотите тестировать ваши запросы напрямую, см. Как тестировать хранилища Doctrine.

Макетирование 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. Таким образом, в тестировании не участвует ни один настоящий класс.

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

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

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

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
# app/config/config_test.yml
doctrine:
    # ...
    dbal:
        host:     localhost
        dbname:   testdb
        user:     testdb
        password: testdb

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