【问题标题】:Laravel Testing Increasing Memory UseLaravel 测试增加内存使用
【发布时间】:2016-12-30 00:22:44
【问题描述】:
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();

【问题讨论】:

标签: php laravel phpunit


【解决方案1】:

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

phpunit.xml 文件中将<phpunit> 标记processIsolation 属性值更改为true。一个正确的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 添加到测试类的文档块中:

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

【讨论】:

    【解决方案2】:

    我为此苦苦挣扎了很长时间,通过开启进程隔离暂时解决了这个问题。这种方法很有效,但速度非常慢并导致了其他问题。

    高内存使用是因为 Laravel 应用程序对象包含对其他对象的许多引用。 PHP 不擅长垃圾收集具有许多相互关联的引用的对象,因此这些对象会四处游荡,导致大量内存使用。

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

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

    添加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 添加到 autoload > classmap 确保 Laravel 自动加载新类。您可能还想添加app/start/global.php (ClassLoader::addDirectories) 的路径。

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

    所有这一切都是在每次测试之后调用clean() 方法,实质上是在对象被垃圾回收之前删除引用。

    【讨论】:

    • 升级到 5.3 后,我不得不放弃我原来的答案解决方案,并接受了你的建议,但必须删除父级拆解,否则一切正常
    • 如果您将方法命名为 flush() 而不是 clean() 您可以调用 parent::tearDown(); 而无需其他 3 行。父 tearDown 调用了一个flush() 方法,所以通过这种方式你可以有效地覆盖它。
    【解决方案3】:

    这是一个简单的解决方案。我在 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 测试运行的所有测试完成后重置数据库。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-27
        • 2014-12-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多