改进CakePHP 3单元测试性能

问题描述 投票:0回答:1

我的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);
    }
cakephp cakephp-3.0
1个回答
0
投票

我改变了单元/集成测试的工作方式。这适用于为每个测试用例设置和拆除数据库(对于我可能添加的大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。

© www.soinside.com 2019 - 2024. All rights reserved.