Symfony UnitTest - тестирование сервисов

scorpion-ds

Новичок
Для разработки логики, решил прибегнуть к UnitTest для тестирования, так как визуального интерфейса еще нет, а делать что-то надо.

Есть сервис, который содержит такой метод:
PHP:
public function createSheet(Office $office, \DateTime $date)
    {
        if($this->getSheetByOffice($office, $date) instanceof Sheet)
        {
            return false;
        }

        $setDate = new \DateTime('first day of ' . $date->format('F') . ' ' . $date->format('Y'));

        $sheet = new Sheet();
        $sheet
            ->setDate($setDate)
            ->setOffice($office)
            ->setTitle($office->getId() . '_' . $date->format('Y-m'))
        ;

        $this->entityManager->persist($sheet);

        try
        {
            $this->entityManager->flush();

            // Event - create <<
            $event = new FilterSheetEvent($sheet);
            $this->dispatcher->dispatch(SheetEvent::onCreate, $event);
            // Event - create >>

        }
        catch(\Doctrine\DBAL\Exception\ConstraintViolationException $e)
        {
            error_log($e->getMessage());
            return false;
        }

        $this->categoryManager->createRoot($sheet);

        return $sheet;
    }
Он должен создать объект Sheet, при этом ему назначается объект Office.

При обычном использовании, все происходит как надо, но если это запустить через UnitTest:
PHP:
    public function testCreateSheet()
    {
        $office = $this->getContainer()->get('fc.office.manager')->getOffice($this->officeId);

        // Проверям смогли получить центр учета
        $this->assertInstanceOf('\FC\OfficeBundle\Entity\Office', $office);


        $date = new \DateTime();
        //$date->add(new \DateInterval('P1M'));

        $sheet = $this->getSheetManager()->createSheet($office, $date);

        // Успешное создание
        $this->assertInstanceOf('\FC\CFMBundle\Entity\Sheet', $sheet, 'Method "public function createSheet(Office $office, \DateTime $date) - we expect success"');

        //$this->assertNotInstanceOf('\FC\CFMBundle\Entity\Sheet', $sheet, 'Method "public function createSheet(Office $office, \DateTime $date) - we expect failure"');

    }
То объект Sheet создается (записывается в БД), но при этом почему-то создается новый объект Office и уже его ID присваивается в Sheet.

Входящие объект Office точно правильный, так как:
PHP:
->setTitle($office->getId() . '_' . $date->format('Y-m'))
формируется title с правильным ID, но вот при передачи:
PHP:
->setOffice($office)
происходит какая-то ерунда с созданием нового объекта Office.

P.S.: в принципе у меня нет опыта в работе с UnitTest, потому возможно я не понимаю, что-то очевидное ...
 

stalxed

Новичок
Юнит тесты должны работать без БД.
Все взаимодействия с БД сделай через репозиторий.

Будет следующее:
PHP:
interface SheetRepositoryInterface
{
    public function add(Sheet $sheet);
}
И две реализации:
InMemorySheetRepository - для юнит тестов!
DoctrineSheetRepository - для реальной работы

В юнит тестах контейнер тоже использовать нельзя! Создавай этот сервис вручную, передавая зависимости через конструктор.
 

Adelf

Administrator
Команда форума
Чистые юнит-тесты не лазят в базу. Они подменяют репозитории моками и тестят лишь бизнес-логику, а не то, как криво работает твой ORM.
 

scorpion-ds

Новичок
Я этого не знал, спасибо за разъяснение.

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

hell0w0rd

Продвинутый новичок
Ох тролли. Тестируй сразу API, это будут функциональные тесты. Юнит-тесты тут ни к чему.
А функциональные гораздо проще писать и поддерживать. Шлешь POST /todos, затем через GET /todos проверяешь, что todo есть. Ну и так далее.
Потом можно менять базу/язык/прочие технологии, а тесты останутся как и были.
 

scorpion-ds

Новичок
А функциональные гораздо проще писать и поддерживать. Шлешь POST /todos, затем через GET /todos проверяешь, что todo есть. Ну и так далее.
Потом можно менять базу/язык/прочие технологии, а тесты останутся как и были.
Я уже обдумывал такой вариант, но там почти все на Ajax, часть на Angular, не слишком удобно все это имитировать.
 

Adelf

Administrator
Команда форума
если у тебя нормальное API к которому обращается Angular, то сам бог велел функциональными тестами крыть это API.
 

Активист

Активист
Команда форума
Я уже обдумывал такой вариант, но там почти все на Ajax, часть на Angular, не слишком удобно все это имитировать.
Функциональные тесты, сервисы, юзер интерфейсы хорошо тестить http://codeception.com/ ом.
PHPUnit'ом я иногда тестирую модели (принять оплату по такому-то счету, например). Запускаю модель, отрабатываю с тестовой БД, тригеры там, функции MySQL выполняет, далее создаю объект из БД и стандартными моделями и проверяю его свойства (проценты, откаты, статусы). Это когда очень сложная логика (завязаны объекты, контрагенты, тригеры и т.п.). Но многие функциональные тесты все-таки перекладываю на кодесепшен.
 

AmdY

Пью пиво
Команда форума
Я уже обдумывал такой вариант, но там почти все на Ajax, часть на Angular, не слишком удобно все это имитировать.
наоборот, это очень удобно. и фикстуры набивать очень удобно, ставишь лог в файл всех запросов, побегал по сайту и потом подсовывай данные в httpBackend
 

stalxed

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

hell0w0rd

Продвинутый новичок
Но функциональные тесты лишь дополняют юнит тесты, а уж явно никак не заменяют.
Просто, если domain layer никак четко не отделен от остального кода, то юнит тесты практически невозможно написать(всё будет в чертовых моках, и будет создаваться впечатление, что вы тестируйте не свой код, а код мок генератора).
вот уж не согласен. Если пишешь библиотеку - да, ее надо покрывать юнит-тестами. А на бизнес логику они скорее всего нафиг не нужны.
PHP:
public function indexAction(PostRepository $pr) {
  $posts = $pr->findLatest();
  return $this->serialize($posts);
}
Тест на это будет выглядеть так: мокаем PostRepository, мокаем serialize, проверяем что был вызван findLatest и результат передан в serialize, а его результат возвращен. Ну и нафига это надо?

Юнит тесты надо писать на сервисы, отвязанные от бизнес-логики.
 
  • Like
Реакции: AmdY

stalxed

Новичок
@hell0w0rd, в твоём примере просто нет бизнес логики.
И вообще это код контролера, и да, для него бы я юнит тесты не стал писать.
Юнит тесты нужны для domain layer, библиотечного кода.
 
Сверху