English 中文(简体)
如何在Symfony2 使用 PHP 单元建立数据库重体测试?
原标题:How to set up database-heavy unit tests in Symfony2 using PHPUnit?

我对测试世界是相当新奇的,我想确保我走在正确的轨道上。

我试图在 symfony2 项目中使用 phpunit 设置单位测试。

PHPUMunit 正在工作,简单的默认控制器测试效果良好。 (但这不是关于功能测试,而是关于单位测试我的应用程序。 )

我的项目在很大程度上依赖数据库的相互作用,据我所知,从phpunits documents ,我应该根据PHPUnit_Extensions_Database_TestCase 设立一个班,然后为我的Db和在那里工作创建固定装置。

然而,symfony2 仅提供一个 WebTesticas 类,仅从框外PHPUnit_Framework_TestCase 延伸。

因此,我是否有理由假设,我应该创建我自己的 DataBaseTesticas ,其中多数复制了 WebTesticas ,只有区别在于它与PHPUnid_Extension_Database_TestCase 的相去甚远,并采用其所有抽象方法?

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

由于我想确保我的模型储存和检索正确的数据,我不想最终偶然地测试doctrine 的具体细节。

最佳回答

tl; dr:

  • If and only if you want to go the whole functional test route, then I recommend looking up Sgoettschkes s answer.
  • 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
    (There is a more up-to-date version also talking about testing with fixtures for symfony5)


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).

单位测试只对服务而不是仓库有意义。 而这些服务可以使用实体经理的模型。 (我甚至会说:如果可能的话,写一些只期望实体被传递给他们的服务。然后,你只需要创建这些实体的模型,而你的单位测试你的商业逻辑就会变得非常直截了当。 )

我在申请中的实际使用案例在上“https://symfony.com/doc/2.8/suggest/database.html” rel=“nofollow noreferrer” 上“ymfony2 doccs>测试与数据库互动的代码 的方法上得到了很好的反映。

它们为服务测试提供了这个例子:

服务级别 :

use DoctrineCommonPersistenceObjectManager;

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 TestsAppBundleSalary;

use AppBundleSalarySalaryCalculator;
use AppBundleEntityEmployee;
use DoctrineORMEntityRepository;
use DoctrineCommonPersistenceObjectManager;

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 采集器提取正确的数据。 我可以从经验中推断正确的数据将被存储 。

有些情况是,功能测试非常重要,也就是说,如果一个人要写一个在线商店的话。 买到的物品被存储到数据库中,而在这里,用整个测试数据库进行功能测试是绝对有道理的。

问题回答

我从未使用过PHPUM_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.

理论上我的方式...

我用 https://github.com/doctrine/DoctrineFixturesBundle" rel=“noreferrer”>doctrine/doctrine-fixtures-bundle 用于固定装置(不管目的为何),并用所有固定装置建立整个数据库。然后,我对数据库进行所有测试,并确保在测试改变数据库时重新创建数据库。

其优点是,如果测试只读而不更改任何内容,我不需要再建立数据库。对于修改,我必须将其放下,再创建一次,或者确保恢复修改。

我使用 Sqlite 进行测试, 因为我可以建立数据库, 然后复制 sqlite 文件, 并用干净的文件替换它, 以恢复原始数据库 。 这样我就不必丢弃数据库, 创建数据库, 并重新装入所有固定装置, 以建立一个干净的数据库 。

< 坚固 >. 代码

我写了一篇""http://sgoettschkes.blogspot.com/2012/06/symfony2-test-database-best-pratice.html" rel=“noreferr” 的文章,介绍我如何用交响乐2和phpunit 进行数据库测试。

虽然它使用Sqlite,但我认为人们可以很容易地进行修改,以便使用 MySQL 或 Postgres 或什么的。

< 坚固 > 进一步考虑

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

  • 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.
  • 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.
  • 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.

您可以使用此类 :

<?php

namespace ProjectBundleTests;

require_once dirname(__DIR__). /../../../app/AppKernel.php ;

use DoctrineORMToolsSchemaTool;

abstract class TestCase extends PHPUnit_Framework_TestCase
{
/**
* @var SymfonyComponentHttpKernelAppKernel
*/
protected $kernel;

/**
 * @var DoctrineORMEntityManager
 */
protected $entityManager;

/**
 * @var SymfonyComponentDependencyInjectionContainer
 */
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 DoctrineDBALSchemaSchemaException( 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:theodo.fr/blog/2011/09/symfony2-unit-database-tests。





相关问题
Brute-force/DoS prevention in PHP [closed]

I am trying to write a script to prevent brute-force login attempts in a website I m building. The logic goes something like this: User sends login information. Check if username and password is ...

please can anyone check this while loop and if condition

<?php $con=mysql_connect("localhost","mts","mts"); if(!con) { die( unable to connect . mysql_error()); } mysql_select_db("mts",$con); /* date_default_timezone_set ("Asia/Calcutta"); $date = ...

定值美元

如何确认来自正确来源的数字。

Generating a drop down list of timezones with PHP

Most sites need some way to show the dates on the site in the users preferred timezone. Below are two lists that I found and then one method using the built in PHP DateTime class in PHP 5. I need ...

Text as watermarking in PHP

I want to create text as a watermark for an image. the water mark should have the following properties front: Impact color: white opacity: 31% Font style: regular, bold Bevel and Emboss size: 30 ...

How does php cast boolean variables?

How does php cast boolean variables? I was trying to save a boolean value to an array: $result["Users"]["is_login"] = true; but when I use debug the is_login value is blank. and when I do ...

热门标签