Ми}{@лbI4

Блог хеллоуворлдщика

Изолированный запуск тестов Codeception

22.07.2018 codeception, php, testing

Данное решение подойдет только для тех, кто разбил тесты на модули, а не тестирует сразу весь проект.

Когда ваш проект большой и все ООП, то это кушает много памяти. А вы еще, как на зло, заюзали некогда очень популярный среди разработчиков на PHP - Codeception. Я не буду здесь критиковать Codeception, личный выбор каждого, но, ИМХО, если встанет выбор между Codeception и PHPUnit, то выбирайте второе. ;) Но, здесь мы собрались по другой причине...

Причина написания данной статьи является выход таймаута или переполнение памяти при запуске тестов, что никак не решается в Codeception из коробки, но прекрасно решается в PHPUnit.

Но, все решаемо и я поделюсь с вами небольшим куском кода, который вы можете доработать, или использовать, как есть.

Для работы над понадобятся следующие пакеты:

А также, Composer и codeception.yml в корне проекта, где перечислены модули для тестирования в секции include.

Создадим файл /bin/iso-test с таким содержанием:

#!/usr/bin/env php
<?php

require_once __DIR__ '/../vendor/autoload.php';

use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml as YamlDecoder;
use Zend\Config\Reader\Yaml as YamlConfig;

$codeceptionConfig = (new YamlConfig([YamlDecoder::class, 'parse']))->fromFile(__DIR__ . '/../codeception.yml');

$beginTime = new DateTime();
$totalMemory = [];

$k = count($codeceptionConfig['include']);
$i = 1;
foreach ($codeceptionConfig['include'] as $name) {
    echo colorStr(sprintf("(%d/%d) Wait...\n", $i++, $k), 'blue');

    $process = new Process(
        __DIR__ . '/../vendor/bin/codecept run --ansi -c ' . $name,
        null,
        null,
        null,
        300
    );
    $process->start();

    $process->wait(function ($type, $buffer) {
        echo $buffer;
    });

    if (!$process->isSuccessful()) {
        echo $process->getErrorOutput();
        exit($process->getExitCode());
    }

    $totalMemory[] = fetchMemory($process->getOutput());

    echo PHP_EOL;
}

echo colorStr('Done!', 'green') . PHP_EOL;
echo colorStr(
    sprintf(
        'Time: %s, Memory: %.2fMB',
        (new DateTime())->diff($beginTime)->format('%H:%I:%S'),
        calcMemory($totalMemory)
    ),
    'cyan'
) . PHP_EOL;

function fetchMemory(string $output): array {
    $matches = [];
    preg_match('/Memory: (\d+(\.\d+|)(\w+))/', $output, $matches);
    return [$matches[3], (float)$matches[1]];
}

function calcMemory(array $totalMemory): float {
    $result = [];

    foreach ($totalMemory as $memory) {
        switch ($memory[0]) {
            case 'GB':
                $result[] = $memory[1] * 1024;
                break;

            case 'MB':
                $result[] = $memory[1];
                break;

            case 'KB':
                $result[] = $memory[1] / 1024;
                break;

            default:
                throw new DomainException('Fatal error while calculation total memory');
        }
    }

    return array_sum($result) / count($result);
}

function colorStr($str, $code)
{
    return sprintf('%s[%dm%s%s[0m', chr(27), getColorCode($code), $str, chr(27));
}

function getColorCode($name)
{
    $styles = [
        'red' => 31,
        'green' => 32,
        'yellow' => 33,
        'blue' => 34,
        'magenta' => 35,
        'cyan' => 36,
        'white' => 37,
    ];
    return $styles[$name];
}

Файл codeception.yml должен содержать секцию include, т.к. именно по этой секции мы и запускаем наши тесты изолированно:

include:
    - module/Application
    - module/Amazon
    # и еще куча других модулей
paths:
    output: _codeceptOutput

По результата запуска php bin/iso-test вы можете получить такой вывод в случае успеха:

Done!
Time: 00:13:01, Memory: 49.58MB

, где указано общее время выполнения всех тестов и среднее значение использованной памяти. Если во время выполнения произошла ошибка и тест не прошел, то процесс будет прерван.

Данное решение хорошо спасает, т.к. аналогично запущенные тесты просто через vendor/bin/codecept run потребовали бы минимум 1024Мб свободной оперативной памяти и данные число постоянно бы росло, как на дрожжах.