Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP ZF2 Unit Tests dispatch method very slow

I need to test a big site written in ZF2. There is 443 test and about 10000 assertions. Testing with code coverage takes 6 hours! I think I found the problem: In controller's tests I use a dispatch method from AbstractHttpControllerTestCase. Time of execution of dispatch method is increasing after each test (from fractions of second to tens of seconds).

I use ZF 2.1.3, PHPUnit 3.7, PHP_CodeCoverage 1.2, Xdebug v2.2.1, PHP 5.4.7.

My dispatch method:

public function dispatch($url, $method = HttpRequest::METHOD_GET, $params = array())
{
    $s = microtime(true);

    parent::dispatch($url, $method, $params);

    $end = microtime(true) - $s;
    echo 'dis: '.$end."\n";

    return $this->getApplication()->getMvcEvent()->getResult();
}

parent::dispatch is method from AbstractHttpControllerTestCase.

Sample of test:

$result = $this->dispatch('/archive/finance/older');

$this->assertControllerName('skycontent\controller\article');
$this->assertActionName('archive');
$this->assertParamValue('older', true);
$this->assertParamValue('category', 'finance');

$vars = (array) $result->getVariables();

$this->assertArrayHasKey('archivePosts', $vars);

Please help. Thanks.


Update:

I use process isolation and tests done in about 15 minutes (without code coverage) but I get error in the test that are marked as skipped:

PHPUnit_Framework_Exception: PHP Fatal error:  Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed' in -:44
like image 354
Kudlaty Avatar asked Mar 13 '13 10:03

Kudlaty


1 Answers

Both Zend\ServiceManager and Zend\EventManager make heavy use of closures. You cannot serialize an entire application instance and expect that to work, since it would basically mean that you attempt to serialize service factories and event listeners defined as closures.

The solution may be to use a test Bootstrap.php like the one of DoctrineORMModule, which doesn't keep an application instance in memory. Here's a simplified example:

require_once __DIR__ . '/../vendor/autoload.php';

$appConfig = require __DIR__ . '/TestConfiguration.php';

\YourModuleTest\Util\ServiceManagerFactory::setConfig($appConfig);

unset($appConfig);

(the TestConfiguration should look like a standard mvc application's config)

You also need the ServiceManagerFactory. Sample implementation can be found here and here.

namespace YourModuleTest\Util;

class ServiceManagerFactory
{
    /**
     * @var array
     */
    protected static $config;

    /**
     * @param array $config
     */
    public static function setConfig(array $config)
    {
        static::$config = $config;
    }

    /**
     * Builds a new service manager
     * Emulates {@see \Zend\Mvc\Application::init()}
     */
    public static function getServiceManager()
    {
        $serviceManager = new ServiceManager(new ServiceManagerConfig(
            isset(static::$config['service_manager']) 
                ? static::$config['service_manager'] 
                : array()
        ));

        $serviceManager->setService('ApplicationConfig', static::$config);
        $serviceManager->setFactory(
            'ServiceListener',
            'Zend\Mvc\Service\ServiceListenerFactory'
        );

        /** @var $moduleManager \Zend\ModuleManager\ModuleManager */
        $moduleManager = $serviceManager->get('ModuleManager');
        $moduleManager->loadModules();

        return $serviceManager;
    }
}

Now, wherever you want in your tests, you can:

$serviceManager = \YourModuleTest\Util\ServiceManagerFactory::getServiceManager();
$application    = $serviceManager->get('Application');

$application->bootstrap();

// ...

With this setup, you can run tests in insulation.

On the other side, you should focus on real unit tests first, since ZF2 really simplifies how you can compose complex objects. You should also correctly setup the coverage filters so that unrelated code is not handled (that may eat up a lot of time).

Also, re-using the mvc application instance is wrong, since the helpers are not stateless, which makes it hard to reuse them.

like image 71
Ocramius Avatar answered Sep 28 '22 10:09

Ocramius