Улучшение производительности модульного теста CakePHP 3 - PullRequest
0 голосов
/ 31 августа 2018

Модульные тесты мучительно медленны для моего проекта CakePHP 3.5. У нас есть большой объем данных, которые необходимо загрузить в приборы, чтобы адекватно протестировать наше приложение. У нас есть 50 приборов, которые загружены в 5 различных классах Integration Test, и 78 тестов для этих классов. Это может занять несколько минут.

Схема приборов загружается из нашей базы данных, а записи заполняются с использованием публичной переменной records, например:

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);
    }

1 Ответ

0 голосов
/ 03 сентября 2018

Я изменил работу наших модульных / интеграционных тестов. Это работает вокруг настройки и разрушения базы данных для каждого тестового примера (который, как я могу добавить, является БЕЗУМНЫМ нет-нет для больших О). Это длинный ответ:

Тесты / bootstrap.php

Мы собираемся определить наши приборы в файле начальной загрузки и собрать их с помощью нового класса, расширяющего Менеджер приспособлений (подробнее об этом позже). В конце файла начальной загрузки мы заставим соединение использовать нашу тестовую базу данных.

<?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,00 МБ

ОК, но неполные, пропущенные или рискованные тесты! Тесты: 111, Утверждения: 802, Неполные: 21.

...