我对测试世界很陌生,我想确保我走在正确的轨道上。
我正在尝试使用 phpunit 在 symfony2 项目中设置单元测试。
PHPUnit 正在运行,简单的默认控制器测试工作正常。 (但这不是关于功能测试,而是单元测试我的应用程序。)
我的项目在很大程度上依赖于数据库交互,据我所知phpunit's documentation,我应该基于\PHPUnit_Extensions_Database_TestCase
建立一个类,然后为我的db创建fixtures并从那里开始工作。
然而, symfony2 仅提供WebTestCase
类,该类仅从\PHPUnit_Framework_TestCase
开箱即用。
所以我认为我应该创建自己的DataBaseTestCase
主要复制WebTestCase
,但唯一的区别在于它是从\PHPUnit_Extensions_Database_TestCase
延伸并实现其所有抽象方法吗?
或者 symfony2 还有另一个“内置”推荐的工作流程,涉及以数据库为中心的测试吗?
由于我想确保我的模型存储和检索正确的数据,我不想最终意外地测试 doctrine 的细节。
答案 0 :(得分:35)
我从未使用PHPUnit_Extensions_Database_TestCase
,主要原因是出于以下两个原因:
我在理论上的方式......
我使用doctrine/doctrine-fixtures-bundle作为灯具(无论用途),并使用所有灯具设置整个数据库。然后,我对该数据库执行所有测试,并确保在测试更改时重新创建数据库。
优点是,如果测试仅读取但不更改任何内容,则无需再次设置数据库。对于我必须做的更改,请将其删除并再次创建或确保还原更改。
我使用sqlite进行测试,因为我可以设置数据库,然后复制sqlite文件并用干净的文件替换它以恢复原始数据库。这样我就不必丢弃数据库,创建它并再次加载所有夹具以获得干净的数据库。
...并在代码中
我写了article about how I do database tests with symfony2 and phpunit。
虽然它使用sqlite,但我认为可以轻松地进行更改以使用MySQL或Postgres等等。
进一步思考
以下是其他可能有用的想法:
答案 1 :(得分:3)
TL; DR:
<小时/> 在我的原始问题中有某些方面清楚地表明我对单元测试和功能测试之间的差异的理解是缺乏的。 (正如我所写,我想对应用程序进行单元测试,但同时也在讨论Controller测试;这些是通过定义进行的功能测试)。
单元测试仅对服务有意义,而对存储库没有意义。这些服务可以使用实体管理器的模拟。 (我甚至会说:如果可能的话,编写只希望将实体传递给它们的服务。然后你只需要创建那些实体的模拟,你的业务逻辑的单元测试变得非常简单。)
我的应用程序的实际用例很好地反映在how to test code that interacts with the database上的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解析器提取正确的数据。我可以从经验中推断出将存储正确的数据。
在某些情况下,功能测试非常重要,即如果要编写在线商店。在那里购买物品存储到数据库中是至关重要的,这里使用整个测试数据库的功能测试是绝对有意义的。
答案 2 :(得分: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