我的CakePHP 3.5项目的单元测试速度非常慢。我们有大量的数据必须加载到灯具中,以便充分测试我们的应用。我们有50个灯具在5个不同的集成测试类中加载,并且在这些类中共有78个测试。这些运行需要几分钟。
fixtures模式从我们的数据库加载,使用记录公共变量填充记录,例如:
class AmenityFixture extends TestFixture
{
public $table = 'amenity';
public $import = ['connection' => 'default', 'model' => 'Amenity'];
/**
* Init method
*
* @return void
*/
public function init()
{
$this->records = [
[
'id' => 1,
'amenity_category_id' => 8,
'name' => 'Air conditioning',
'slug' => 'air-conditioning',
'status' => 'active',
'created' => '2013-06-03 11:07:30',
'modified' => '2013-06-18 12:17:29'
],
是否有可能,只需加载一次灯具,可能通过tests / boostrap.php然后在测试退出时将它们拆掉?我见过这种事情的引用,但仅限于Cake 2。
更多信息我知道它的灯具,因为如果我通过退出设置方法强制创建灯具,然后注释掉灯具,我们最大的集成测试控制器在1.23秒内运行测试。如果它必须从头开始创建灯具,则需要2分钟。我想知道Cake是否正在为每个测试或每个控制器创建灯具?我只需要运行一次这些灯具,即使每个控制器运行一次(如果它们确实按照每种测试方法运行)也会是一个巨大的改进。
以下是测试类和示例测试方法的示例:
<?php
namespace Api\Test\TestCase\Controller;
use Api\Controller\BookController;
use Cake\TestSuite\IntegrationTestCase;
use Cake\Network\Http\Client;
use Cake\Utility\Security;
use Cake\Core\Configure;
use App\Logic\BookingLogic;
use App\Legacy\CodeIgniter\CI_Encrypt;
/**
* Api\Controller\DefaultController Test Case
* @example vendor/bin/phpunit plugins/Api
*/
class BookControllerTest extends IntegrationTestCase
{
/**
* Fixtures
*
* @var array
*/
public $fixtures = [
'app.amenity',
'app.amenity_category',
'app.api_log',
'app.area',
'app.area_filter',
'app.area_geocode',
'app.area_publisher',
'app.area_property',
'app.country',
'app.discount',
'app.error_log',
'app.feature',
'app.filter',
'app.organization',
'app.publisher',
'app.publisher_key',
'app.publisher_property',
'app.publisher_property_feature',
'app.publisher_property_order',
'app.promotion',
'app.promotion_blackout',
'app.promotion_property_accommodation',
'app.promotion_purchase_requirement',
'app.promotion_type',
'app.promotion_stay_requirement',
'app.property',
'app.property_accommodation',
'app.property_accommodation_publisher_exception',
'app.property_accommodation_rate',
'app.property_accommodation_rate_log',
'app.property_accommodation_rate_availability',
'app.property_accommodation_rate_availability_log',
'app.property_accommodation_image',
'app.property_accommodation_provider_attribute_value',
'app.property_accommodation_type',
'app.property_address',
'app.property_address_geocode',
'app.property_amenity',
'app.property_description',
'app.property_discount',
'app.property_discount_option',
'app.property_image',
'app.property_fee',
'app.property_filter',
'app.property_policy',
'app.property_provider',
'app.property_provider_attribute_value',
'app.property_rating',
'app.property_telephone',
'app.property_type',
'app.provider',
'app.provider_attribute',
'app.reservation',
'app.reservation_confirmation',
'app.reservation_customer',
'app.reservation_customer_address',
'app.reservation_customer_telephone',
'app.reservation_idx',
'app.reservation_payment',
'app.reservation_promotion',
'app.reservation_property_accommodation',
'app.reservation_property_accommodation_discount',
'app.reservation_property_accommodation_rate',
'app.state',
];
public function setUp(){
$this->checkin = date('Y-m-d', strtotime('+2 days'));
$this->checkout = date('Y-m-d', strtotime('+6 days'));
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
}
public function testAvailability(){
$this->post('/publisher/v3.0/book/preview.json',json_encode([
'property_id' => 1111,
'accommodation_id' => 1303,
'rate_code' => 'REZ',
'checkin' => $this->checkin,
'checkout' => $this->checkout,
'rooms' => [
['adults' => 2, 'children' => 0],
]
]));
$this->assertEquals(958.64, $rate->total->total);
}
我改变了单元/集成测试的工作方式。这适用于为每个测试用例设置和拆除数据库(对于我可能添加的大O,这是一个INSANE no-no)。这个答案很长:
测试/ bootstrap.php中
我们将在bootstrap文件中定义我们的fixture并使用扩展Fixture Manager的新类来构建它们(稍后会详细介绍)。在引导程序文件的末尾,我们将强制连接使用我们的测试数据库。
<?php
use App\Logic\FixtureMaker;
use Cake\Datasource\ConnectionManager;
require dirname(__DIR__) . '/config/bootstrap.php';
$fixtures = [
'app.all_your_table_fixtures',
'app.right_here',
];
$connection = ConnectionManager::get('test');
$connection->disableForeignKeys();
$fixtureMaker = new FixtureMaker();
echo "\r\nBuilding fixtures\r\n";
foreach ($fixtures as $fixture) {
$table = str_replace('app.', '', $fixture);
$model = Cake\Utility\Inflector::camelize($table);
$fixture = $model . 'Fixture';
$fixtureMaker->pushFixture($model, $fixture);
$fixtureMaker->loadSingle($model, $connection, false);
}
unset($fixtureMaker); // i was getting pdo errors without this
unset($connection);
echo "\r\nFixtures completed\r\n";
ConnectionManager::alias('test', 'default');
FixtureMaker
您可以将它放在任何位置,对于我们它在App \ Logic \ FixtureMaker命名空间中。它只需要扩展FixtureManager,这样我们就可以使用它的受保护方法,即loadSingle()。我们还需要填充受保护的属性_loaded和_fixtureMap。很基本的。
<?php
namespace App\Logic;
use Cake\TestSuite\Fixture\FixtureManager;
class FixtureMaker extends FixtureManager
{
public function pushFixture($class, $fixture)
{
$className = "App\\Test\\Fixture\\" . $fixture;
$this->_loaded[$fixture] = new $className();
$this->_fixtureMap[$class] = $this->_loaded[$fixture];
}
}
phpunit.xml.dist
没有什么可以疯狂的,只需删除Cake默认放在那里的监听器。如果这些听众留在那里,那么Cake会尝试拍打自己的灯具等等,我们不希望这样。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
colors="true"
processIsolation="false"
stopOnFailure="true"
syntaxCheck="false"
bootstrap="./tests/bootstrap.php"
>
<php>
<ini name="memory_limit" value="-1"/>
<ini name="apc.enable_cli" value="1"/>
</php>
<!-- Add any additional test suites you want to run here -->
<testsuites>
<testsuite name="App Test Suite">
<directory>./tests/TestCase</directory>
<directory>./plugins/Api/tests</directory>
<directory>./plugins/Adapter/tests</directory>
</testsuite>
<!-- Add plugin test suites here. -->
</testsuites>
<!-- Setup a listener for fixtures -->
<listeners>
</listeners>
<!-- Ignore vendor tests in code coverage reports -->
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
<directory suffix=".php">./plugins/*/src/</directory>
</whitelist>
</filter>
</phpunit>
赛程
从各种测试类中删除对灯具的所有引用。您可以根据自己的需要完成实际的夹具类,对我来说,我选择在生产模式中读取但是创建自定义记录。这是首选,因为我永远不必更新夹具架构。如果需要为测试添加/编辑记录,我只需要修改现有的夹具。这是一个示例夹具:
<?php
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
/**
* PromotionTypeFixture
*
*/
class PromotionTypeFixture extends TestFixture
{
public $table = 'promotion_type';
public $import = ['connection' => 'default', 'model' => 'PromotionType'];
/**
* Init method
*
* @return void
*/
public function init()
{
$this->records = [
[
'id' => 1,
'name' => '$ off',
'created' => '2012-11-05 13:02:09'
],
[
'id' => 2,
'name' => '% off',
'created' => '2012-11-05 13:02:14'
],
[
'id' => 3,
'name' => 'Free Night',
'created' => '2012-11-05 13:02:18'
],
];
parent::init();
}
}
遗言
这是一个巨大的痛苦,但它将单位测试的执行时间从近3分钟(并以残酷的速度增长)降低到13秒。我们在这里有相当数量的测试:
时间:13.13秒,内存:36.00MB
好的,但不完整,跳过或有风险的测试!测试:111,断言:802,不完整:21。