如何使用 PHPUnit 在 Symfony2 中设置大量数据库单元测试?

2022-01-25 00:00:00 php symfony phpunit doctrine-orm

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 is working and the simple default controller tests work fine. (Yet this is not about functional testing but unit testing my application.)

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.

Yet, symfony2 only offers a WebTestCase class which only extends from PHPUnit_Framework_TestCase out of the box.

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?

Or is there another "built-in" recommended workflow for symfony2 concerning database-centric tests?

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.

解决方案

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

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.

They provide this example for a service test:

Service class:

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

Service test class:

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

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.

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.

相关文章