Как настроить модульные тесты в Symfony2 с использованием PHPUnit?

41

Я новичок в мире тестирования, и я хочу убедиться, что я на правильном пути.

Я пытаюсь настроить модульные тесты в проекте symfony2 с помощью phpunit.

PHPUnit работает, и простые тесты контроллера по умолчанию работают нормально. (Тем не менее, речь идет не о функциональном тестировании, а модульном тестировании моего приложения.)

Мой проект в значительной степени зависит от взаимодействия с базами данных, и, насколько я понимаю из документации phpunit, я должен создать класс на основе \PHPUnit_Extensions_Database_TestCase, затем создайте приспособления для моего db и работайте оттуда.

Тем не менее, symfony2 предлагает только класс WebTestCase, который выходит только из \PHPUnit_Framework_TestCase из коробки.

Как я могу предположить, что я должен создать свой собственный DataBaseTestCase, который в основном копирует WebTestCase, только разница в том, что он простирается от \PHPUnit_Extensions_Database_TestCase и реализует все его абстрактные методы?

Или существует ли еще один "встроенный" рекомендуемый рабочий процесс для symfony2 в отношении тестов, ориентированных на базу данных?

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

  • 0
    Я работаю над той же проблемой. Есть удача?
  • 0
    @JasonSwett Нет. Я только что получил награду из-за отсутствия удовлетворительного ответа.
Теги:
phpunit
doctrine2

3 ответа

2
Лучший ответ

TL;DR:

  • Если и только если вы хотите пройти весь функциональный тестовый маршрут, я рекомендую посмотреть ответ Sgoettschkes.
  • Если вы хотите unit test ваше приложение и должны проверить код, который взаимодействует с базой данных, либо прочитайте, либо перейдите прямо к symfony2 docs


В моем первоначальном вопросе были определенные аспекты, которые дают понять, что мое понимание различий между модульным тестированием и функциональными тестами отсутствует. (Как я уже писал, я хочу использовать unit test приложение, но одновременно говорил и о тесте Controller, и это функциональный тест путем defintion).

Единичное тестирование имеет смысл только для служб, а не для репозиториев. И эти службы могут использовать mocks менеджера сущностей.

Мой фактический прецедент для моего приложения на самом деле довольно хорошо отразился на документах symfony2 на как протестировать код, который взаимодействует с данным databse, Они предоставляют этот пример для теста обслуживания:

Класс обслуживания:

use Doctrine\Common\Persistence\ObjectManager;

class SalaryCalculator
{
    private $entityManager;

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

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

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

Класс обслуживания:

namespace Tests\AppBundle\Salary;

use AppBundle\Salary\SalaryCalculator;
use AppBundle\Entity\Employee;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectManager;

class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase
{
    public function testCalculateTotalSalary()
    {
        // First, mock the object to be used in the test
        $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));

        // Now, mock the repository so it returns the mock of the employee
        $employeeRepository = $this
            ->getMockBuilder(EntityRepository::class)
            ->disableOriginalConstructor()
            ->getMock();
        $employeeRepository->expects($this->once())
            ->method('find')
            ->will($this->returnValue($employee));

        // Last, mock the EntityManager to return the mock of the repository
        $entityManager = $this
            ->getMockBuilder(ObjectManager::class)
            ->disableOriginalConstructor()
            ->getMock();
        $entityManager->expects($this->once())
            ->method('getRepository')
            ->will($this->returnValue($employeeRepository));

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

Нет тестовой базы данных, необходимой для такого теста, только (болезненным) насмешкой.

Как важно проверить бизнес-логику, а не уровень сохранения.

Только для функционального теста имеет смысл иметь собственную тестовую базу данных, которую следует строить и сносить потом, и большой вопрос должен быть:

Когда функциональный тест имеет смысл?

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

Предположим, у меня есть приложение, которое анализирует XML, создает объект из него и сохраняет эти объекты в базе данных. Если логика, которая хранит объекты в базе данных, как известно, работает (как в: компания требует данных и пока еще не сломалась), и даже если эта логика является большой уродливой кучей дерьма, нет неотложная необходимость проверить это. Поскольку все, что мне нужно, чтобы убедиться, что мой синтаксический анализатор XML извлекает правильные данные. Я могу сделать вывод, что нужные данные будут сохранены.

Существуют сценарии, где функциональный тест очень важен, т.е. если нужно писать интернет-магазин. Там было бы критически важным для бизнеса, что купленные предметы будут храниться в базе данных, и здесь функциональный тест со всей тестовой базой данных имеет абсолютный смысл.

33

Я никогда не использовал PHPUnit_Extensions_Database_TestCase, главным образом потому, что по двум причинам:

  • Он плохо масштабируется. Если вы настроили и разорвали базу данных для каждого отдельного теста, и у вас есть приложение, которое в значительной степени зависит от базы данных, вы в конечном итоге создаете и отбрасываете одну и ту же схему снова и снова.
  • Мне нравится иметь свои приборы не только в моих тестах, но также и в моей базе данных разработки, и некоторые инструменты нужны даже для производства (начальный пользователь admin или категории товаров или что-то еще). Наличие их внутри xml, который может использоваться только для phpunit, мне не кажется правильным.

Мой путь в теории...

Я использую doctrine/doctrine-fixtures-bundle для светильников (независимо от цели) и настроил всю базу данных со всеми приспособлениями. Затем я выполняю все тесты против этой базы данных и не забудьте создать базу данных, если тест изменил ее.

Преимущества в том, что мне больше не нужно настраивать базу данных, если тест читает только, но ничего не меняет. Для изменений мне нужно сбросить его и создать его снова или обязательно вернуть изменения.

Я использую sqlite для тестирования, потому что я могу настроить базу данных, а затем скопировать файл sqlite и заменить его на чистый, чтобы вернуть исходную базу данных. Таким образом, мне не нужно бросать базу данных, создавать ее и снова загружать все приборы для чистой базы данных.

... и в коде

Я написал статью о том, как я делаю тесты базы данных с symfony2 и phpunit.

Хотя он использует sqlite, я думаю, что можно легко внести изменения в использование MySQL или Postgres или что-то еще.

Мышление дальше

Вот некоторые другие идеи, которые могут сработать:

  • Я когда-то читал о тестовой установке, где перед использованием базы данных вы запускаете транзакцию (в рамках метода setUp), а затем используете откат для отката. Таким образом, вам не нужно снова настраивать базу данных и просто нужно ее инициализировать один раз.
  • У моей установки, описанной выше, есть недостаток, который база данных настраивается каждый раз, когда выполняется phpunit, даже если вы выполняете только отдельные модульные тесты без взаимодействия с базой данных. Я экспериментирую с установкой, где я использую глобальную переменную, которая указывает, была ли установлена ​​база данных, а затем в рамках тестов вызывается метод, который проверяет эту переменную и инициализирует базу данных, если она еще не была выполнена. Таким образом, только когда тесты нуждаются в базе данных, установка будет происходить.
  • Одна из проблем с sqlite заключается в том, что в некоторых редких случаях она не работает так же, как MySQL. У меня возникла проблема, когда в MySQL и SQLite что-то было по-другому, что привело к сбою теста при работе с MySQL. Я не могу вспомнить, что это было точно.
  • 7
    Существует пакет, который обеспечивает дальнейшую интеграцию между PHPUnit и фикстурами Doctrine и называется Liip / FunctionalTestBundle . Мы используем это в сочетании с базой данных sqlite, определенной в нашей конфигурации config_test.yml, и это помогает многое упростить! Одна действительно полезная особенность пакета заключается в том, что он может кэшировать вашу базу данных sqlite, поэтому база данных не разделяется между тестами, что приводит к уменьшению накладных расходов, связанных с созданием и настройкой базы данных для каждого теста.
  • 0
    Что касается различий между SQLite и MySQL, я думаю, что в первую очередь вы сталкиваетесь с функциями даты и времени, такими как NOW (), которые не работают в SQLite. В моем блоге недавно появилось сообщение об использовании SQLite для модульного тестирования, в котором описываются некоторые из этих проблем cvuorinen.net/2012/10/…
Показать ещё 1 комментарий
0

Вы можете использовать этот класс:

<?php

namespace Project\Bundle\Tests;

require_once dirname(__DIR__).'/../../../app/AppKernel.php';

use Doctrine\ORM\Tools\SchemaTool;

abstract class TestCase extends \PHPUnit_Framework_TestCase
{
/**
* @var Symfony\Component\HttpKernel\AppKernel
*/
protected $kernel;

/**
 * @var Doctrine\ORM\EntityManager
 */
protected $entityManager;

/**
 * @var Symfony\Component\DependencyInjection\Container
 */
protected $container;


public function setUp()
{
    // Boot the AppKernel in the test environment and with the debug.
    $this->kernel = new \AppKernel('test', true);
    $this->kernel->boot();

    // Store the container and the entity manager in test case properties
    $this->container = $this->kernel->getContainer();
    $this->entityManager = $this->container->get('doctrine')->getEntityManager();

    // Build the schema for sqlite
    $this->generateSchema();


    parent::setUp();
}

public function tearDown()
{
    // Shutdown the kernel.
    $this->kernel->shutdown();

    parent::tearDown();
}

protected function generateSchema()
{
    // Get the metadatas of the application to create the schema.
    $metadatas = $this->getMetadatas();

    if ( ! empty($metadatas)) {
        // Create SchemaTool
        $tool = new SchemaTool($this->entityManager);
        $tool->createSchema($metadatas);
    } else {
        throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.');
    }
}

/**
 * Overwrite this method to get specific metadatas.
 *
 * @return Array
 */
protected function getMetadatas()
{
    return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}

И затем вы можете протестировать свою сущность. Что-то вроде этого (при условии, что у вас есть объект пользователя)

//Entity Test
class EntityTest extends TestCase {

    protected $user;

    public function setUp()
    {
         parent::setUp();
         $this->user = new User();
         $this->user->setUsername('username');
         $this->user->setPassword('p4ssw0rd');


         $this->entityManager->persist($this->user);
         $this->entityManager->flush();

    }

    public function testUser(){

         $this->assertEquals($this->user->getUserName(), "username");
         ...

    }

}

Надеюсь на эту помощь.

Источник: theodo.fr/blog/2011/09/symfony2-unit-database-tests

Ещё вопросы

Сообщество Overcoder
Наверх
Меню