Laravel测试增加内存使用

时间:2016-08-23 08:56:15

标签: php laravel phpunit

Time: 1.89 minutes, Memory: 526.00MB

OK (487 tests, 2324 assertions)

这是我测试我的Laravel API的phpunittest结果,内存消耗不断增加,感觉我在互联网上尝试了所有帖子和答案,以便在测试时保持内存。从自己的调试开始,每次测试都会破坏应用程序。

一切都非常标准,createApplication方法看起来像这样。

public function createApplication()
{
    // Ran out of memory
    ini_set('memory_limit', '1024M');

    $app = require __DIR__ . '/../bootstrap/app.php';

    $app->make(Kernel::class)->bootstrap();

    return $app;
}

得出的结论是内存泄漏,无法正常清理。

$app = require __DIR__ . '/../bootstrap/app.php';

$app->make(Kernel::class)->bootstrap();

5 个答案:

答案 0 :(得分:3)

这是我发现的最简单,最可靠的解决方案。它没有my previous answer中描述的缺点。

<phpunit>文件中的processIsolation代码true属性值更改为phpunit.xml。正确phpunit.xml文件的开头示例:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="true"
         stopOnFailure="false">
...

工作原理:它使PhpUnit为每个测试启动一个新的PHP进程。在单独的进程中运行测试会强制PHP在测试结束后释放所有内存。它会降低测试速度,但这是低内存消耗和可靠性的代价。

或者,如果您需要在单独的进程中运行某些测试,则可以将@runTestsInSeparateProcesses annotation添加到测试类的doc块中:

/**
 * @runTestsInSeparateProcesses
 */
class HeavyTest extends TestCase
{
    // ...
}

答案 1 :(得分:2)

我长期以来一直在努力通过打开流程隔离暂时解决问题。这种方式有效,但速度超慢,引发了其他问题。

高内存使用率是因为Laravel应用程序对象包含许多对其他对象的引用。 PHP不是很擅长垃圾收集具有许多互连引用的对象,因此对象会挂起而导致大量内存使用。

我通过扩展内置的Application对象并添加我自己的每次测试后调用的清理方法来解决这个问题:

注意:这适用于Laravel 4.2,但同样的主体应该与Laravel 5 +一样好。

添加add_filter('woocommerce_package_rates', 'wf_hide_shipping_method_based_on_shipping_class', 10, 2); function wf_hide_shipping_method_based_on_shipping_class($available_shipping_methods, $package) { $hide_when_shipping_class_exist = array( 42 => array( 'free_shipping' ) ); $hide_when_shipping_class_not_exist = array( 42 => array( 'wf_shipping_ups:03', 'wf_shipping_ups:02', 'wf_shipping_ups:01' ), 43 => array( 'free_shipping' ) ); $shipping_class_in_cart = array(); foreach(WC()->cart->cart_contents as $key => $values) { $shipping_class_in_cart[] = $values['data']->get_shipping_class_id(); } foreach($hide_when_shipping_class_exist as $class_id => $methods) { if(in_array($class_id, $shipping_class_in_cart)){ foreach($methods as & $current_method) { unset($available_shipping_methods[$current_method]); } } } foreach($hide_when_shipping_class_not_exist as $class_id => $methods) { if(!in_array($class_id, $shipping_class_in_cart)){ foreach($methods as & $current_method) { unset($available_shipping_methods[$current_method]); } } } return $available_shipping_methods; } (根据您使用命名空间的方式,这可能会有所不同)。

app\foundation\Application.php

通过修改<?php namespace App\Foundation; class Application extends \Illuminate\Foundation\Application { /** * Used during unit tests to clear out all the data structures * that may contain references. * Without this phpunit runs out of memory. */ public function clean() { unset($this->bootingCallbacks); unset($this->bootedCallbacks); unset($this->finishCallbacks); unset($this->shutdownCallbacks); unset($this->middlewares); unset($this->serviceProviders); unset($this->loadedProviders); unset($this->deferredServices); unset($this->bindings); unset($this->instances); unset($this->reboundCallbacks); unset($this->resolved); unset($this->aliases); } } 文件并将composer.json添加到app/foundation&gt;确保Laravel自动加载新课程。 autoload。您可能还想将路径添加到classmap(ClassLoader :: addDirectories)。

修改app/start/global.php并将bootstrap\start.php更改为$app = new Illuminate\Foundation\Application;

现在编辑您的$app = new App\Foundation\Application;并添加您自己的app/tests/TestCase.php方法:

tearDown

所有这一切都是在每次测试之后调用public function tearDown() { $this->app->clean(); unset($this->app); unset($this->client); parent::tearDown(); } 方法,基本上在对象被垃圾回收之前删除引用。

答案 2 :(得分:1)

这是一个简单的解决方案。我在Laravel 5.5中测试过它。将其设置为tests/CreatesApplication.php文件的内容:

namespace Tests;

use Illuminate\Contracts\Console\Kernel;

trait CreatesApplication
{
    /**
     * @var \Illuminate\Foundation\Application
     */
    protected static $application;

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        if (!isset(static::$application)) {
            static::$application = require __DIR__ . '/../bootstrap/app.php';
            static::$application->make(Kernel::class)->bootstrap();
        }

        return static::$application;
    }

    /**
     * {@inheritDoc}
     */
    protected function tearDown()
    {
        // Required if you use HTTP tests
        $this->app['auth']->guard(null)->logout();
        $this->app['auth']->shouldUse(null);
        $this->flushSession();

        // Always required
        $this->app = null;
        parent::tearDown();
    }
}

此代码使测试对所有测试使用相同的Laravel应用程序实例,而不是为每个测试创建新实例。这也会导致更快的测试运行。

这种解决方案有缺点:

  • 如果测试修改了应用程序,则其他测试可能会失败,因为他们期望未更改的应用程序。
  • 如果测试使用DatabaseTransactionsDatabaseMigrations特征,则在完成PHPUnit测试运行的所有测试时,将重置数据库。

答案 3 :(得分:1)

我有一个类似的问题,但这是由于在单元测试期间启用了Telescope。在phpunit.xml中添加以下内容可将我的内存使用量减少约75%。

<server name="TELESCOPE_ENABLED" value="false"/>

答案 4 :(得分:0)

全球化我的$app变量解决了我的问题,在某些情况下,这可以提供有趣的代码隔离结果,但在我的情况下效果很好。正如这里所建议的那样,在Laracast Post由wiseloren。

global $app;
if (is_null($app)) {
    $app = require __DIR__ . '/../bootstrap/app.php';

    $app->make(Kernel::class)->bootstrap();
}
return $app;

我的Phpunit统计数据现在已降至,同时显着降低了整个过程中的运行时间。

Time: 27.18 seconds, Memory: 42.00MB

OK (509 tests, 2494 assertions)

Process finished with exit code 0