I am quite new to the world of testing and I want to make sure I am on the right track.
我对测试世界很陌生,我想确保我走在正确的轨道上。
I am trying to setup unit tests in a symfony2 project using phpunit.
我正在尝试使用phpunit在symfony2项目中设置单元测试。
PHPUnit is working and the simple default controller tests work fine. (Yet this is not about functional testing but unit testing my application.)
PHPUnit正在运行,简单的默认控制器测试工作正常。 (但这不是功能测试,而是单元测试我的应用程序。)
My project relies heavily on database interactions though, and as far as I understand from phpunit's documentation, I should set up a class based on \PHPUnit_Extensions_Database_TestCase
, then create fixtures for my db and work from there.
我的项目在很大程度上依赖于数据库交互,据我所知,从phpunit的文档中,我应该基于\ PHPUnit_Extensions_Database_TestCase设置一个类,然后为我的db创建fixtures并从那里开始工作。
Yet, symfony2 only offers a WebTestCase
class which only extends from \PHPUnit_Framework_TestCase
out of the box.
然而,symfony2只提供一个WebTestCase类,它只能从\ PHPUnit_Framework_TestCase开箱即用。
So am I right to assume that I should create my own DataBaseTestCase
which mostly copies WebTestCase
, only difference being that it extends from \PHPUnit_Extensions_Database_TestCase
and implements all its abstract methods?
所以我是正确的假设我应该创建自己的DataBaseTestCase,它主要复制WebTestCase,唯一的区别是它从\ PHPUnit_Extensions_Database_TestCase扩展并实现其所有抽象方法?
Or is there another "built-in" recommended workflow for symfony2 concerning database-centric tests?
或者是否有另一个“内置”推荐的symfony2工作流程涉及以数据库为中心的测试?
As I want to make sure that my models store and retrieve the right data, I do not want to end up testing the specifics of doctrine by accident.
由于我想确保我的模型存储和检索正确的数据,我不想最终意外地测试学说的细节。
3 个解决方案
#1
35
I have never used the PHPUnit_Extensions_Database_TestCase
, mostly because for these two reasons:
我从未使用过PHPUnit_Extensions_Database_TestCase,主要是因为这两个原因:
- It doesn't scale well. If you set up and tear down the database for every single test and you have a application which relies heavily on the database, you end up creating and dropping the same schema over and over again.
- 它不能很好地扩展。如果您为每个测试设置并拆除数据库,并且您拥有一个严重依赖于数据库的应用程序,那么您最终会一次又一次地创建和删除相同的模式。
- I like to have my fixtures not only within my tests but also within my development database and some fixtures are even needed for production (initial admin user or product categories or whatever). Having them inside an xml which can only be used for phpunit doesn't seem right to me.
- 我喜欢不仅在我的测试中,而且在我的开发数据库中都有我的灯具,甚至还需要一些灯具用于制作(初始管理员用户或产品类别或其他)。将它们放在一个只能用于phpunit的xml里面对我来说似乎不对。
My way in theory...
我理论上的方式......
I use the doctrine/doctrine-fixtures-bundle for fixtures (no matter what purpose) and set up the whole database with all fixtures. I then execute all tests against this database and make sure to recreate the database if a test changed it.
我使用doctrine / doctrine-fixtures-bundle作为灯具(无论用途),并使用所有灯具设置整个数据库。然后,我对该数据库执行所有测试,并确保在测试更改时重新创建数据库。
The advantages are that I don't need to set up a database again if a test only reads but don't change anything. For changes I have to do drop it and create it again or make sure to revert the changes.
优点是,如果测试仅读取但不更改任何内容,则无需再次设置数据库。对于更改,我必须删除它并再次创建它或确保还原更改。
I use sqlite for testing because I can set up the database, then copy the sqlite file and replace it with a clean one to bring back the original database. That way I don't have to drop the database, create it and load all fixtures again for a clean database.
我使用sqlite进行测试,因为我可以设置数据库,然后复制sqlite文件并用干净的文件替换它以恢复原始数据库。这样我就不必丢弃数据库,创建它并再次加载所有夹具以获得干净的数据库。
...and in code
......并且在代码中
I wrote an article about how I do database tests with symfony2 and phpunit.
我写了一篇关于如何使用symfony2和phpunit进行数据库测试的文章。
Although it uses sqlite I think one can easily make the changes to use MySQL or Postgres or whatever.
虽然它使用sqlite我认为可以轻松地进行更改以使用MySQL或Postgres或其他任何东西。
Thinking further
进一步思考
Here are some other ideas which might work:
以下是一些可能有用的其他想法:
- I once read about a test setup where before you use the database you start a transaction (within the setUp method) and then use the tearDown to rollback. That way you don't need to set up the database again and just need to initialize it once.
- 我曾经读过关于测试设置的地方,在你使用数据库之前,你开始一个事务(在setUp方法中),然后使用tearDown进行回滚。这样您就不需要再次设置数据库,只需要初始化一次。
- My setup described above has the drawback that the database is set up every time phpunit is executed, even if you only run some unit tests with no database interaction. I am experimenting with a setup where I use a global variable which indicates if the database was set up and then within the tests call a method whcih checks this variable and initializes the database if it didn't happened yet. That way only when a tests needs the database the setup would happen.
- 我上面描述的设置有一个缺点,即每次执行phpunit时都会设置数据库,即使你只运行了一些没有数据库交互的单元测试。我正在尝试一个设置,其中我使用一个全局变量来指示数据库是否已设置,然后在测试中调用一个方法来检查此变量并初始化数据库(如果尚未发生)。这样只有当测试需要数据库时才会进行设置。
- One problem with sqlite is that it doesn't work the same as MySQL in some rare cases. I had an issue once where something behaved different in MySQL and sqlite causing a test to fail when in with MySQL everything worked. I cannot remember what it was exactly.
- sqlite的一个问题是在某些极少数情况下它与MySQL的工作方式不同。我有一个问题,一旦MySQL和sqlite中的某些行为表现不同,导致测试失败,当使用MySQL时一切正常。我不记得究竟是什么。
#2
3
tl;dr:
TL;博士:
- If and only if you want to go the whole functional test route, then I recommend looking up Sgoettschkes's answer.
- 当且仅当你想要进入整个功能测试路线时,我建议查找Sgoettschkes的答案。
- If you want to unit test your application and have to test code that interacts with the database, either read on or jump directly to symfony2 docs
- 如果您想对应用程序进行单元测试并且必须测试与数据库交互的代码,请继续阅读或直接跳转到symfony2 docs
There were certain aspects in my original question that make it clear that my understanding of the differences between unit testing and functional tests was lacking. (As I have written I want to unit test the application, yet was also talking about Controller test at the same time; and those are functional test by defintion).
Unit testing only makes sense for services and not for repositories. And those services can use mocks of the entity manager. (I would even go as far as to say: If possible, write services that only expect entities to be passed into them. Then you only need to create mocks of those entities and your unit-tests of your business logic become very straightforward.)
单元测试仅对服务而不是存储库有意义。这些服务可以使用实体管理器的模拟。 (我甚至会说:如果可能的话,编写只希望将实体传递给它们的服务。然后你只需要创建那些实体的模拟,你的业务逻辑的单元测试变得非常简单。)
My actual use case for my application was pretty well reflected on the symfony2 docs on how to test code that interacts with the database.
我的应用程序的实际用例很好地反映在symfony2文档上,该文档介绍了如何测试与数据库交互的代码。
They provide this example for a service test:
他们为服务测试提供了这个示例:
Service class:
服务类:
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();
}
}
Service test class:
服务测试类:
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));
}
}
No test database required for those kind of test, only mocking.
这些测试不需要测试数据库,只需要模拟。
As it is important to test the business logic, not the persistence layer.
因为测试业务逻辑很重要,而不是持久层。
Only for functional tests does it make sense to have its own test database that one should build and tear down afterwards, and the big question should be:
只有在进行功能测试时才有自己的测试数据库才能构建并在之后拆除,最重要的问题应该是:
When do functional test make sense?
什么时候功能测试有意义?
I used to think that test all the things is the right answer; yet after working with lots of legacy software that in itself was barely test-driven developed I have become a bit more lazypragmatic and consider certain functionality as working until proven otherwise by a bug.
我曾经认为测试所有的东西都是正确的答案;然而,在使用了许多本身几乎没有测试驱动开发的遗留软件之后,我已经变得更加懒散,并且认为某些功能正常工作,直到被错误证明为止。
Assume I have an application that parses an XML, creates an object out of it, and stores those objects into a database. If the logic that stores the objects to the database is known to work (as in: the company requires the data and is, as of yet, not broke), and even if that logic is a big ugly pile of crap, there is no imminent need to test that. As all I need to make sure that my XML parser extracts the right data. I can infer from experience that the right data will be stored.
假设我有一个解析XML的应用程序,从中创建一个对象,并将这些对象存储到数据库中。如果知道将对象存储到数据库的逻辑可以工作(如:公司需要数据并且尚未破坏),即使该逻辑是一堆丑陋的垃圾,也没有迫切需要测试一下。因为我需要确保我的XML解析器提取正确的数据。我可以从经验中推断出将存储正确的数据。
There are scenarios where functional test are quite important, i.e. if one were to write an online shop. There it would be business critical that bought items get stored into the database and here functional tests with the whole test database make absolute sense.
在某些情况下,功能测试非常重要,即如果要编写在线商店。在那里购买物品存储到数据库中是至关重要的,这里使用整个测试数据库的功能测试是绝对有意义的。
#3
0
You can use this class:
你可以使用这个类:
<?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();
}
}
And then you can test your entity. Something like this (assuming you have a entity User)
然后你可以测试你的实体。这样的事情(假设你有一个实体用户)
//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");
...
}
}
Hope this help.
希望这有帮助。
Source: theodo.fr/blog/2011/09/symfony2-unit-database-tests
资料来源:theodo.fr/blog/2011/09/symfony2-unit-database-tests
#1
35
I have never used the PHPUnit_Extensions_Database_TestCase
, mostly because for these two reasons:
我从未使用过PHPUnit_Extensions_Database_TestCase,主要是因为这两个原因:
- It doesn't scale well. If you set up and tear down the database for every single test and you have a application which relies heavily on the database, you end up creating and dropping the same schema over and over again.
- 它不能很好地扩展。如果您为每个测试设置并拆除数据库,并且您拥有一个严重依赖于数据库的应用程序,那么您最终会一次又一次地创建和删除相同的模式。
- I like to have my fixtures not only within my tests but also within my development database and some fixtures are even needed for production (initial admin user or product categories or whatever). Having them inside an xml which can only be used for phpunit doesn't seem right to me.
- 我喜欢不仅在我的测试中,而且在我的开发数据库中都有我的灯具,甚至还需要一些灯具用于制作(初始管理员用户或产品类别或其他)。将它们放在一个只能用于phpunit的xml里面对我来说似乎不对。
My way in theory...
我理论上的方式......
I use the doctrine/doctrine-fixtures-bundle for fixtures (no matter what purpose) and set up the whole database with all fixtures. I then execute all tests against this database and make sure to recreate the database if a test changed it.
我使用doctrine / doctrine-fixtures-bundle作为灯具(无论用途),并使用所有灯具设置整个数据库。然后,我对该数据库执行所有测试,并确保在测试更改时重新创建数据库。
The advantages are that I don't need to set up a database again if a test only reads but don't change anything. For changes I have to do drop it and create it again or make sure to revert the changes.
优点是,如果测试仅读取但不更改任何内容,则无需再次设置数据库。对于更改,我必须删除它并再次创建它或确保还原更改。
I use sqlite for testing because I can set up the database, then copy the sqlite file and replace it with a clean one to bring back the original database. That way I don't have to drop the database, create it and load all fixtures again for a clean database.
我使用sqlite进行测试,因为我可以设置数据库,然后复制sqlite文件并用干净的文件替换它以恢复原始数据库。这样我就不必丢弃数据库,创建它并再次加载所有夹具以获得干净的数据库。
...and in code
......并且在代码中
I wrote an article about how I do database tests with symfony2 and phpunit.
我写了一篇关于如何使用symfony2和phpunit进行数据库测试的文章。
Although it uses sqlite I think one can easily make the changes to use MySQL or Postgres or whatever.
虽然它使用sqlite我认为可以轻松地进行更改以使用MySQL或Postgres或其他任何东西。
Thinking further
进一步思考
Here are some other ideas which might work:
以下是一些可能有用的其他想法:
- I once read about a test setup where before you use the database you start a transaction (within the setUp method) and then use the tearDown to rollback. That way you don't need to set up the database again and just need to initialize it once.
- 我曾经读过关于测试设置的地方,在你使用数据库之前,你开始一个事务(在setUp方法中),然后使用tearDown进行回滚。这样您就不需要再次设置数据库,只需要初始化一次。
- My setup described above has the drawback that the database is set up every time phpunit is executed, even if you only run some unit tests with no database interaction. I am experimenting with a setup where I use a global variable which indicates if the database was set up and then within the tests call a method whcih checks this variable and initializes the database if it didn't happened yet. That way only when a tests needs the database the setup would happen.
- 我上面描述的设置有一个缺点,即每次执行phpunit时都会设置数据库,即使你只运行了一些没有数据库交互的单元测试。我正在尝试一个设置,其中我使用一个全局变量来指示数据库是否已设置,然后在测试中调用一个方法来检查此变量并初始化数据库(如果尚未发生)。这样只有当测试需要数据库时才会进行设置。
- One problem with sqlite is that it doesn't work the same as MySQL in some rare cases. I had an issue once where something behaved different in MySQL and sqlite causing a test to fail when in with MySQL everything worked. I cannot remember what it was exactly.
- sqlite的一个问题是在某些极少数情况下它与MySQL的工作方式不同。我有一个问题,一旦MySQL和sqlite中的某些行为表现不同,导致测试失败,当使用MySQL时一切正常。我不记得究竟是什么。
#2
3
tl;dr:
TL;博士:
- If and only if you want to go the whole functional test route, then I recommend looking up Sgoettschkes's answer.
- 当且仅当你想要进入整个功能测试路线时,我建议查找Sgoettschkes的答案。
- If you want to unit test your application and have to test code that interacts with the database, either read on or jump directly to symfony2 docs
- 如果您想对应用程序进行单元测试并且必须测试与数据库交互的代码,请继续阅读或直接跳转到symfony2 docs
There were certain aspects in my original question that make it clear that my understanding of the differences between unit testing and functional tests was lacking. (As I have written I want to unit test the application, yet was also talking about Controller test at the same time; and those are functional test by defintion).
Unit testing only makes sense for services and not for repositories. And those services can use mocks of the entity manager. (I would even go as far as to say: If possible, write services that only expect entities to be passed into them. Then you only need to create mocks of those entities and your unit-tests of your business logic become very straightforward.)
单元测试仅对服务而不是存储库有意义。这些服务可以使用实体管理器的模拟。 (我甚至会说:如果可能的话,编写只希望将实体传递给它们的服务。然后你只需要创建那些实体的模拟,你的业务逻辑的单元测试变得非常简单。)
My actual use case for my application was pretty well reflected on the symfony2 docs on how to test code that interacts with the database.
我的应用程序的实际用例很好地反映在symfony2文档上,该文档介绍了如何测试与数据库交互的代码。
They provide this example for a service test:
他们为服务测试提供了这个示例:
Service class:
服务类:
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();
}
}
Service test class:
服务测试类:
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));
}
}
No test database required for those kind of test, only mocking.
这些测试不需要测试数据库,只需要模拟。
As it is important to test the business logic, not the persistence layer.
因为测试业务逻辑很重要,而不是持久层。
Only for functional tests does it make sense to have its own test database that one should build and tear down afterwards, and the big question should be:
只有在进行功能测试时才有自己的测试数据库才能构建并在之后拆除,最重要的问题应该是:
When do functional test make sense?
什么时候功能测试有意义?
I used to think that test all the things is the right answer; yet after working with lots of legacy software that in itself was barely test-driven developed I have become a bit more lazypragmatic and consider certain functionality as working until proven otherwise by a bug.
我曾经认为测试所有的东西都是正确的答案;然而,在使用了许多本身几乎没有测试驱动开发的遗留软件之后,我已经变得更加懒散,并且认为某些功能正常工作,直到被错误证明为止。
Assume I have an application that parses an XML, creates an object out of it, and stores those objects into a database. If the logic that stores the objects to the database is known to work (as in: the company requires the data and is, as of yet, not broke), and even if that logic is a big ugly pile of crap, there is no imminent need to test that. As all I need to make sure that my XML parser extracts the right data. I can infer from experience that the right data will be stored.
假设我有一个解析XML的应用程序,从中创建一个对象,并将这些对象存储到数据库中。如果知道将对象存储到数据库的逻辑可以工作(如:公司需要数据并且尚未破坏),即使该逻辑是一堆丑陋的垃圾,也没有迫切需要测试一下。因为我需要确保我的XML解析器提取正确的数据。我可以从经验中推断出将存储正确的数据。
There are scenarios where functional test are quite important, i.e. if one were to write an online shop. There it would be business critical that bought items get stored into the database and here functional tests with the whole test database make absolute sense.
在某些情况下,功能测试非常重要,即如果要编写在线商店。在那里购买物品存储到数据库中是至关重要的,这里使用整个测试数据库的功能测试是绝对有意义的。
#3
0
You can use this class:
你可以使用这个类:
<?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();
}
}
And then you can test your entity. Something like this (assuming you have a entity User)
然后你可以测试你的实体。这样的事情(假设你有一个实体用户)
//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");
...
}
}
Hope this help.
希望这有帮助。
Source: theodo.fr/blog/2011/09/symfony2-unit-database-tests
资料来源:theodo.fr/blog/2011/09/symfony2-unit-database-tests