如何使用PHPUnit在Symfony2中设置数据库繁重的unit testing?

我对testing世界相当陌生,我想确保自己走在正确的道路上。

我正在尝试使用phpunitsymfony2项目中设置unit testing。

PHPUnit正在工作,简单的默认控制器testing工作正常。 (然而,这不是关于functiontesting,而是unit testing我的应用程序。)

尽pipe我的项目很大程度上依赖于数据库交互,而据我所知,从phpunit的文档中 ,我应该build立一个基于\PHPUnit_Extensions_Database_TestCase ,然后为我的数据库创build灯具,并从那里工作。

然而, symfony2只提供了一个WebTestCase类,它只能从\PHPUnit_Framework_TestCase开箱即用。

那么我是否应该假设我应该创build自己的DataBaseTestCase ,它主要复制WebTestCase ,区别在于它从\PHPUnit_Extensions_Database_TestCase扩展并实现了它的所有抽象方法?

还是有另一个“内置的” symfony2推荐的工作stream程涉及到数据库为中心的testing?

因为我想确保我的模型存储和检索正确的数据,所以我不希望最后偶然地检验教义的细节。

我从来没有使用PHPUnit_Extensions_Database_TestCase ,主要是因为这两个原因:

  • 它不能很好地扩展。 如果您为每个testing设置并拆除数据库,并且您的应用程序严重依赖于数据库,则最终会一次又一次地创build和删除相同的模式。
  • 我喜欢不仅在我的testing中,而且在我的开发数据库中,还有一些灯具甚至是生产(初始pipe理用户或产品类别或其他)需要的灯具。 把它们放在一个只能用于phpunit的xml里面对我来说并不合适。

我理论上的方式

我使用doctrine / doctrine-fixtures-bundle进行灯具(不pipe是什么目的),并用所有灯具设置整个数据库。 然后我对这个数据库执行所有的testing,并确保在testing改变的时候重新创build数据库。

好处是,如果一个testing只读取但不改变任何东西,我不需要重新build立一个数据库。 对于我所做的更改,请将其删除并重新创build,或确保恢复更改。

我使用sqlite进行testing,因为我可以设置数据库,然后复制sqlite文件并将其replace为干净的数据库。 这样,我不必删除数据库,创build它,并再次加载所有的灯具一个干净的数据库。

…在代码中

我写了一篇关于如何使用symfony2和phpunit进行数据库testing的文章 。

虽然它使用sqlite我认为可以很容易地使用MySQL或Postgres或其他任何更改。

进一步思考

这里有一些可能的工作原理:

  • 我曾经阅读过有关在使用数据库之前启动事务的testing设置(在setUp方法中),然后使用tearDown进行回滚。 这样你就不需要重新设置数据库,只需要初始化一次。
  • 我上面描述的设置有一个缺点,即每次执行phpunit时都会设置数据库,即使你只运行一些没有数据库交互的unit testing。 我正在试验一个设置,我使用一个全局variables来指示数据库是否设置,然后在testing中调用一个方法,检查这个variables,并初始化数据库,如果它还没有发生。 这种方式只有当一个testing需要数据库的安装程序会发生。
  • sqlite的一个问题是它在一些极less数情况下不能和MySQL一样工作。 我有一个问题曾经在MySQL和sqlite中performance不同,导致testing失败时,在MySQL的一切工作。 我不记得那是什么。

TL;博士:

  • 当且仅当您想要进入整个functiontesting路线时,我build议查找Sgoettschkes的答案 。
  • 如果你想unit testing你的应用程序,并且必须testing与数据库交互的代码,请阅读或直接跳转到symfony2文档

在我原来的问题中,有一些方面说得很清楚,我对unit testing和functiontesting之间差异的理解是缺乏的。 (正如我写的,我想unit testing应用程序,但同时也在谈论控制器testing;而那些是定义的functiontesting)。

unit testing只适用于服务而不适用于存储库。 那些服务可以使用实体pipe理器的模拟。

我的应用程序的实际使用情况实际上很好地反映在symfony2文档上, 如何testing与数据库交互的代码 。 他们提供了这个服务testing的例子:

服务等级:

 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(); } } 

服务testing课:

 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)); } } 

这些testing不需要testing数据库,只有(痛苦的)嘲弄。

因为testing业务逻辑非常重要,而不是持久层。

只有functiontesting是有意义的,有一个自己的testing数据库,应该build立和拆除之后,最大的问题应该是:

functiontesting什么时候有意义?

我曾经认为, testing所有的东西是正确的答案; 但在处理了大量本身几乎不受testing驱动开发的遗留软件之后,我变得更加懒惰实用,并且认为某些function正在运行,直到一个bug被certificate为止。

假设我有一个应用程序parsing一个XML,创build一个对象,并将这些对象存储到数据库中。 如果将对象存储到数据库的逻辑已知可以正常工作(如:公司需要数据并且至今尚未中断),即使该逻辑是一大堆废话,也没有迫在眉睫的需要testing。 尽我所能确保我的XMLparsing器提取正确的数据。 我可以从经验中推断出正确的数据将被存储。

有些情况下,functiontesting是非常重要的,也就是说,如果有人写网上商店。 在那里,将购买物品存储到数据库中的业务关键是这样的,在这里对整个testing数据库进行functiontesting是绝对有意义的。

你可以使用这个类:

 <?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(); } } 

然后你可以testing你的实体。 像这样(假设你有一个实体用户)

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