Тестирование с использованием БД

gutsout11

Новичок
Возник спор, можно ли(всмысле не является ли это плохой практикой) использовать дата провайдеры в функциональных тестах с участием БД, как здесь:
PHP:
class OrdersReportTest extends \PHPUnit\Framework\TestCase {
    /**
     * @dataProvider reportDataProvider
     */
    public function testReport(DateTimeImmutable $reportDate, array $orders, string $expectedResult): void
    {
        foreach ($orders as $order) {
            $this->entityManager->persist($order);
        }
        $this->entityManager->flush();

        $result = $this->reportService->getOrdersAmountOnDate($reportDate);
        $this->assertEquals($expectedResult, $result);
    }
    public static function reportDataProvider(): array
    {
        return [
            [
                'reportDate' => new DateTimeImmutable('2019-11-30'),
                'orders' => [
                    new Order(/*amount: */'100', new DateTimeImmutable('2019-11-30')),
                    new Order('10.7', new DateTimeImmutable('2019-11-30')),
                    new Order('30', new DateTimeImmutable('2019-11-29'))
                ],
                'expectedResult' => '110.7'
            ],
            [
                'reportDate' => new DateTimeImmutable('2019-11-30'),
                'orders' => [],
                'expectedResult' => '0'
            ],
        ];
    }
}
Или данные в базу должны заноситься только фикстурами. Или данные должны создаваться (
Код:
new Order('100', new DateTimeImmutable('2019-11-30'))...
) в самом тесте.

Вообще было бы хорошо услышать кто как пишет тесты с реальными данными в БД, увидеть примеры. В сети, по конкретно этой теме, инфы очень мало, а развёрнутых примеров тем более. Буду благодарен за любую инфу🙂
 
Последнее редактирование модератором:

AmdY

Пью пиво
Команда форума
Юнит тесты должны тестировать только одну фичу, тогда их легко поддерживать и они действительно помогают, а не воруют время.
У тебя в тесте есть код по заполнению базы да ещё с привязкой к доктрине и модели Order. Он будет ломаться от каждого чиха, например, когда вы в Order добавите типизацию для amount придётся все тесты править.

Тест должен выглядеть просто. А уж как ты будешь для него данные готовить уже вторично.
PHP:
    /**
 * @dataProvider reportDataProvider
     */

    public function testGetOrdersAmountOnDate(DateTimeImmutable $reportDate, string $expectedResult): void
    {
        $result = $this->reportService->getOrdersAmountOnDate($reportDate);
        $this->assertEquals($expectedResult, $result);
    }
 
Последнее редактирование:

gutsout11

Новичок
Юнит тесты должны тестировать только одну фичу, тогда их легко поддерживать и они действительно помогают, а не воруют время.
У тебя в тесте есть код по заполнению базы да ещё с привязкой к доктрине и модели Order. Он будет ломаться от каждого чиха, например, когда вы в Order добавите типизацию для amount придётся все тесты править.

Тест должен выглядеть просто. А уж как ты будешь для него данные готовить уже вторично.
PHP:
    /**
* @dataProvider reportDataProvider
     */

    public function testGetOrdersAmountOnDate(DateTimeImmutable $reportDate, string $expectedResult): void
    {
        $result = $this->reportService->getOrdersAmountOnDate($reportDate);
        $this->assertEquals($expectedResult, $result);
    }
Спасибо, но есть вопросы:
с привязкой к доктрине
это для примера, там конечно теоретически какой угодно способ персиста может быть, хоть голый sql
А что в этом плохого?
добавите типизацию для amount
Придётся по всей кодовой базе менять, в том числе и в тестах, ну уж что поделать, но разве это не норм?
А уж как ты будешь для него данные готовить уже вторично
Непонятно как ты связываешь $expectedResult с данными теста. Можно пример, пожалуйста.
 

fixxxer

К.О.
Партнер клуба
Я обычно делаю:
1) acceptance tests, для которых есть тестовая база - тут желательно максимальное покрытие всех юзкейсов,
2) юнит-тесты, они тестируют один определенный класс, и им база данных не нужна вообще. Тут без фанатизма, тестирую только то, где это реально имеет смысл.

Тесты типа вышеприведенного вообще не пишу - такие обычно ломаются при любом внутреннем изменении, которое не влияет на работоспособность приложения. Работал на проекте с такими тестами - обычно получалось так: что-то сделал, запушил feature branch, на CI какие-то тесты отвалились, материшься, открываешь эти тесты, механически правишь (скажем, поле добавилось) и коммитишь опять. И таких 99% случаев на 1% когда реально что-то они отловили. :)
 

gutsout11

Новичок
Я обычно делаю:
1) acceptance tests, для которых есть тестовая база - тут желательно максимальное покрытие всех юзкейсов,
2) юнит-тесты, они тестируют один определенный класс, и им база данных не нужна вообще. Тут без фанатизма, тестирую только то, где это реально имеет смысл.

Тесты типа вышеприведенного вообще не пишу - такие обычно ломаются при любом внутреннем изменении, которое не влияет на работоспособность приложения. Работал на проекте с такими тестами - обычно получалось так: что-то сделал, запушил feature branch, на CI какие-то тесты отвалились, материшься, открываешь эти тесты, механически правишь (скажем, поле добавилось) и коммитишь опять. И таких 99% случаев на 1% когда реально что-то они отловили. :)
2) юнит-тесты, они тестируют один определенный класс, и им база данных не нужна вообще
В юнит-тестах да, согласен. Но я про функциональные тесты, я писал 🙂
...в функциональных тестах...
Наверное я пример не совсем удачный привёл, вот другой:
Допустим эндпоинт апи хотим протестировать: GET /report/orders-amount/?date=2019-11-30
PHP:
<?php

class OrdersReportControllerTest extends \PHPUnit\Framework\TestCase {

    /**
     * @dataProvider reportDataProvider
     */
    public function testReportAction(DateTimeImmutable $reportDate, array $orders, string $expectedResult): void
    {
        foreach ($orders as $order) {
            $this->entityManager->persist($order);
        }
        $this->entityManager->flush();
        $this->client->request('GET', "/report/orders-amount/?date={$reportDate->format('Y-m-d')}");
        $amount = json_decode($this->client->getResponse(), true)['amount'];
        $this->assertEquals($expectedResult, $amount);
    }

    public static function reportDataProvider(): array
    {
        return [
            [
                'reportDate' => new DateTimeImmutable('2019-11-30'),
                'orders' => [
                    new Order(/*amount: */'100', new DateTimeImmutable('2019-11-30')),
                    new Order('10.7', new DateTimeImmutable('2019-11-30')),
                    new Order('30', new DateTimeImmutable('2019-11-29'))
                ],
                'expectedResult' => '110.7'
            ],
            [
                'reportDate' => new DateTimeImmutable('2019-11-30'),
                'orders' => [],
                'expectedResult' => '0'
            ],
        ];
    }
}
acceptance tests, для которых есть тестовая база - тут желательно максимальное покрытие всех юзкейсов
Под acceptance вы понимате те тесты что прям в браузере тестят, в любом случае с функциональными они похожи. На счёт тестовой базы, она перенакатывается перед каждым тестом? А то иначе как, например, если предыдущий тест удалит какойн-ибудь заказ за 2019-11-30, то тест в моём примере упадёт. Если перенакатываете, то будет следующий вопрос🙂 На счёт меня, я да, очищаю базу перед каждым функциональным тестом где учавствует база.
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
На счёт тестовой базы, она перенакатывается перед каждым тестом?
Конечно.

Допустим эндпоинт апи хотим протестировать
А, ну это уже другой вопрос - там есть четко определенная схема запроса и ответа. Так нормально.
Я про те случаи, когда так тестируется что-то "посередине" - вот так не надо делать.
 
Сверху