Independent functional tests with LiipFunctionalTe

2019-04-12 01:23发布

问题:

I'm adding tests to a Symfony2 project. Previously I used the same database for dev and test environments, it used a MySQL database already populated with the same data than on the production server.

The tests were working dependently because some tests depended on previous tests. For example if I had a shop website, I added a product in the cart then removed the product from the cart. So I needed to insert data by using a form, before being able to remove it.

Now I want to work with independent functional tests, because that's the recommended way (by one of Symfony2's developers).

I've configured LiipFunctionalTestBundle correctly to use a SQLite database in the test environment and I've started to add fixtures with DoctrineFixturesBundle.

But I don't know how much data I have to load for each functional test. What fixture should I load at the beginning of a test? How to deal with CRUD operations when the entity depends on other entities because of relationships between tables?

Let's say I'm developing a shop, I want a few tests:

  1. The user add some products in its cart
  2. The user remove one product from its cart
  3. The user order the remaining products

Should I create a different fixture for every step? It means that my fixtures will need to exist in many different states: empty cart, cart with one product ordered, etc. It seems correct to me but very time consuming, so I'm wondering if my idea is valid.

回答1:

For each test case is better to load less fixture as possible both for isolation and for performance (the test suite can go very slowly).

When fixture depends each other, you simply manage them with the doctrine reference and link each other, take care of the order also. As Example, suppose the simply user and role relations.

A generic class for manage role fixture:

abstract class BaseLoadRoleData extends AbstractFixture implements OrderedFixtureInterface
{


    public function getOrder()
    {
        return 1;
    }

    protected function createRole(ObjectManager $manager, $rolename)
    {
        $role= new Role();
        $role->setName($rolename);

        $manager->persist($role);
        $manager->flush();
        $this->setReference('role-' . $rolename, $role);
    }
}

A Dedicated class for the Simple Role

class LoadSimpleRoleData extends BaseLoadRoleData
{
    public function load(ObjectManager $manager)
    {
        $this->createRole($manager, Role::SIMPLE);
    }
}

A Dedicated class for the Admin Role

class LoadAdminRoleData extends BaseLoadRoleData
{
    public function load(ObjectManager $manager)
    {
        $this->createRole($manager, Role::ADMIN);
    }

}

And the user: A generic class for manage user fixture:

abstract class BaseLoadUserData extends AbstractFixture implements OrderedFixtureInterface
{

    /**
     * @var ContainerInterface
     */
    private $container;

    /**
     * {@inheritDoc}
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    public function getOrder()
    {
        return 2;
    }

    protected function buildUser($username, $firstName = "",$lastName ="")
    {
        $user= new User();
        $user->setUsername($username);
        $user->setFirstName($firstName);
        $user->setLastName($lastName);

        return $user;

    }
}

A Dedicated class for the Simple User

class LoadSimpleUserData extends BaseLoadUserData {

    /**
     * Load data fixtures with the passed EntityManager
     *
     * @param Doctrine\Common\Persistence\ObjectManager $manager
     */
    function load(ObjectManager $manager)
    {
        $user = $this->buildUser($manager, "simple@example.com");
        $user->addRole($this->getReference('role-'.Role::SIMPLE));
        $manager->persist($user);
        $manager->flush();
        $this->setReference('user-' . "admin@example.com", $user);

    }
}

A Dedicated class for the Admin User

class LoadAdminUserData extends BaseLoadUserData {

    /**
     * Load data fixtures with the passed EntityManager
     *
     * @param Doctrine\Common\Persistence\ObjectManager $manager
     */
    function load(ObjectManager $manager)
    {
        $user = $this->buildUser($manager, "admin@example.com");
        $user->addRole($this->getReference('role-'.Role::ADMIN));
        $manager->persist($user);
        $manager->flush();
        $this->setReference('user-' . "admin@example.com", $user);

    }

Now you can use it separately, as example, based on the Liip Functional Test Bundle:

class LoginControllerTest {

    public function testAdminUserLogin()
    {
        $this->loadFixtures(array(
            'Acme\DemoBundle\DataFixtures\ORM\LoadAdminRoleData',
            'Acme\DemoBundle\DataFixtures\ORM\LoadAdminUserData'
        ));

        // you can now run your functional tests with a populated database
        $client = static::createClient();
        // ...

        // test the login with admin credential
    }

    public function testSimpleUserLogin()
    {
        // add all your fixtures classes that implement
        // Doctrine\Common\DataFixtures\FixtureInterface
        $this->loadFixtures(array(
            'Acme\DemoBundle\DataFixtures\ORM\LoadSimpleRoleData',
            'Acme\DemoBundle\DataFixtures\ORM\LoadSimpleUserData'
        ));

        // you can now run your functional tests with a populated database
        $client = static::createClient();
        // ...

        // test the login with simple user credential

    }

}

Hope this help.