43

我对测试世界很陌生,我想确保我走在正确的轨道上。

我正在尝试使用phpunit在symfony2项目中设置单元测试。

PHPUnit工作正常,简单的默认控制器测试工作正常。(然而,这不是关于功能测试,而是对我的应用程序进行单元测试。)

不过,我的项目严重依赖数据库交互,据我从phpunit 的文档中了解到,我应该建立一个基于的类\PHPUnit_Extensions_Database_TestCase,然后为我的数据库创建固定装置并从那里开始工作。

然而,symfony2只提供了一个仅从开箱即用的WebTestCase类中扩展的类\PHPUnit_Framework_TestCase

所以我是否应该假设我应该创建自己的DataBaseTestCase大部分副本WebTestCase,唯一的区别是它扩展\PHPUnit_Extensions_Database_TestCase并实现了它的所有抽象方法?

或者关于以数据库为中心的测试,是否有另一个“内置”推荐的symfony2工作流程?

由于我想确保我的模型存储和检索正确的数据,我不想最终意外地测试教义的细节。

4

3 回答 3

35

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

  • 它不能很好地扩展。如果您为每个测试都设置和拆除数据库,并且您有一个严重依赖数据库的应用程序,那么您最终会一遍又一遍地创建和删除相同的模式。
  • 我喜欢不仅在我的测试中而且在我的开发数据库中拥有我的固定装置,甚至生产需要一些固定装置(初始管理员用户或产品类别或其他)。将它们放在只能用于 phpunit 的 xml 中对我来说似乎不合适。

理论上我的方法...

我将教义/教义固定装置捆绑包用于固定装置(无论出于何种目的),并使用所有固定装置设置整个数据库。然后,我针对该数据库执行所有测试,并确保在测试更改数据库时重新创建数据库。

优点是如果测试只读取但不更改任何内容,我不需要再次设置数据库。对于更改,我必须删除它并再次创建它,或者确保还原更改。

我使用 sqlite 进行测试,因为我可以设置数据库,然后复制 sqlite 文件并用干净的文件替换它以恢复原始数据库。这样我就不必删除数据库,创建它并再次加载所有固定装置以获得干净的数据库。

...在代码中

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

虽然它使用 sqlite,但我认为可以轻松地进行更改以使用 MySQL 或 Postgres 或其他任何东西。

进一步思考

以下是一些可能有效的其他想法:

  • 我曾经读过一个测试设置,在你使用数据库之前,你开始一个事务(在 setUp 方法中),然后使用 tearDown 回滚。这样你就不需要再次设置数据库,只需要初始化一次。
  • 我上面描述的设置的缺点是每次执行 phpunit 时都会设置数据库,即使您只运行一些没有数据库交互的单元测试。我正在尝试一种设置,其中我使用一个全局变量来指示数据库是否已设置,然后在测试中调用一个方法,该方法检查此变量并在数据库尚未发生时初始化数据库。这样,只有当测试需要数据库时才会发生设置。
  • sqlite 的一个问题是它在某些极少数情况下与 MySQL 的工作方式不同。我曾经遇到过一个问题,即 MySQL 和 sqlite 中的某些行为不同,导致在 MySQL 一切正常时测试失败。我不记得它到底是什么了。
于 2012-09-20T08:34:20.017 回答
3

tl;博士:


我最初的问题中的某些方面清楚地表明,我对单元测试和功能测试之间的区别缺乏理解。(正如我所写的,我想对应用程序进行单元测试,但同时也在谈论控制器测试;这些是定义上的功能测试)。

单元测试只对服务有意义,对存储库没有意义。这些服务可以使用实体管理器的模拟。(我什至会说:如果可能的话,编写只期望将实体传递给它们的服务。然后您只需要创建这些实体的模拟,您的业务逻辑的单元测试就变得非常简单。)

我的应用程序的实际用例很好地反映在 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 解析器提取正确的数据。我可以根据经验推断将存储正确的数据。

在某些情况下,功能测试非常重要,例如,如果要编写一个在线商店。将购买的物品存储到数据库中将是业务关键,在这里使用整个测试数据库进行功能测试是绝对有意义的。

于 2016-02-17T17:44:11.187 回答
-1

你可以使用这个类:

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

于 2012-05-28T16:12:55.283 回答