sqlitesymfonyphpunitparallel-testingliipfunctionaltestbundle

Parallelize functional tests while using a SQLite database


I have added many tests in my Symfony2 project, but now with 53 tests and 176 assertions it takes about a minute. I'm trying to reduce this time because if I enable code coverage report it takes 15 minutes.

$ phpunit -c app/phpunit.xml.dist PHPUnit 4.3.5 by Sebastian Bergmann.
Configuration read from app/phpunit.xml.dist
.....................................................
Time: 59.1 seconds, Memory: 361.00Mb
OK (53 tests, 176 assertions)

I've configured LiipFunctionalTestBundle correctly to use a SQLite database in the test environment (this is recommended by LiipFunctionalTestBundle):

app/config/config_test.yml

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   pdo_sqlite
                path:     %kernel.cache_dir%/test.db

And I've added fixtures with DoctrineFixturesBundle.

Now I have to create tests suites or groups to launch several instances of PHPUnit. But I'm seeing a future problem: how can tests can be parallelized (for example with paratest) if two or more instances of phpunit write and read data in the same SQLite file?

I can Pass a variable to PhpUnit to change the kernel.cache_dir value and create one cache directory per phpunit instance. But it can't be done from the command line, so if I choose this solution I'll have to create several phpunit.xml.dist. I'm looking for a more convenient solution.


Solution

  • I found a workaround by creating as many cache directories as test files.

    I defined 8 test suites to fit with the 8 cores of my CPU:

    <!-- app/phpunit.xml.dist -->
    <?xml version="1.0" encoding="UTF-8"?>
    
    <phpunit
        backupGlobals = "false"
        …
    >
        …
        <testsuites>
            <testsuite name="Group1">
                <file>../src/AcmeBundle/Tests/Command/CommandTest.php</file>
                <file>…</file>
            </testsuite>
            …
            <testsuite name="Group8">
                <file>../src/AcmeBundle/Tests/Controller/AdminControllerTest.php</file>
            </testsuite>
        </testsuites>
    </phpunit>
    

    I created a phpunit.sh script which use export (as suggested by David Harkness in order to define a specific path for each test suite:

    #!/bin/bash
    
    function test() {
        testsuite=$1
        export CACHE_PATH="$testsuite"
        OUTPUT=$(phpunit -c app/phpunit.xml.dist --testsuite $testsuite)
        RETURN=$?
        if [ $RETURN -eq 0 ]
        then
            echo -e -n "\033[01;32m●  OK\t\033[00m \033[01;35m$testsuite\033[00m"
            tail=$(echo "$OUTPUT" | tail -n 3)
            echo -e "\t\"$tail\"" | tr '\n' '\t' | tr -s '\t'
            echo ""
        else
            echo -e "\033[01;31m❌  ERROR\033[00m \033[01;35m$testsuite\033[00m (\033[01;34m$RETURN\033[00m)\033[00m"
            echo "-----"
            echo "$OUTPUT"
            echo "-----"
        fi
    }
    
    for testsuite in $(seq 1 8)
    do
        tester "Group$testsuite" &
    done
    
    # http://unix.stackexchange.com/questions/231602/how-to-detect-that-all-the-child-processes-launched-in-a-script-ended/231608#231608
    wait
    
    echo "Done"
    

    Successful test suites are displayed with with green marks if everything is OK, and the output of phpunit will be truncated to keep a small output. If there is an error it will be displayed.

    Then I added this to app/AppKernel.php:

    /**
     * @see http://symfony.com/fr/doc/2.3/cookbook/configuration/override_dir_structure.html#surcharger-le-repertoire-cache
     */
    public function getCacheDir()
    {
        $cachePath = getenv('CACHE_PATH');
    
        if (
            ($this->environment == 'test')
            &&
            (! empty($cachePath))
        ) {
            return(parent::getCacheDir().'/'.$cachePath.'/');
        }
    
        # else
    
        return parent::getCacheDir();
    }
    

    This code will tell Symfony to create a sub-directory in the cache folder, meaning than multiple instances of PHPUnit won't use the same SQLite file.


    While it works, this solution is not perfect and have some drawbacks: