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();
答案 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应用程序实例,而不是为每个测试创建新实例。这也会导致更快的测试运行。
这种解决方案有缺点:
DatabaseTransactions
或DatabaseMigrations
特征,则在完成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