Как настроить тесты модуляции базы данных в Symfony2 с помощью PHPUnit?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

… и в коде

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

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

Думая дальше

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

  • Однажды я прочитал о тестовой установке, где перед использованием базы данных вы начинаете транзакцию (в рамках метода setUp), а затем используете откат для отката. Таким образом, вам не нужно снова настраивать базу данных и просто нужно ее инициализировать один раз.
  • Моя настройка, описанная выше, имеет тот недостаток, что база данных настраивается каждый раз, когда выполняется phpunit, даже если вы выполняете только отдельные модульные тесты без взаимодействия с базой данных. Я экспериментирую с установкой, где я использую глобальную переменную, которая указывает, была ли установлена ​​база данных, а затем в рамках тестов вызывается метод, который проверяет эту переменную и инициализирует базу данных, если она еще не была выполнена. Таким образом, только когда тесты нуждаются в базе данных, установка будет происходить.
  • Одна из проблем с sqlite заключается в том, что в некоторых редких случаях она не работает так же, как MySQL. У меня возникла проблема, когда в MySQL и SQLite что-то было по-другому, что привело к сбою теста при работе с MySQL. Я не помню, что именно.

ТЛ; др:

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


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

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

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

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

 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 извлекает правильные данные. Я могу сделать вывод, что нужные данные будут сохранены.

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

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

 <?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