Skip to content
...

Покрытие кода

Плагин собирает данные о покрытии кода во время выполнения тестов и генерирует отчёты в стандартных форматах. Отчёты можно использовать в CI-сервисах (Codecov.io, SonarQube, GitHub Actions) и в IDE — например, PhpStorm умеет отображать покрытие прямо в коде по Clover-отчёту.

Требования

Для сбора покрытия нужно одно из PHP-расширений:

  • PCOV — легковесное, быстрое, только построчное покрытие.
  • XDebug ≥ 3.0 в режиме coverage (xdebug.mode=coverage).

Если доступны оба расширения, Testo предпочитает PCOV — у него меньше накладных расходов. Если не установлено ни одно из расширений, поведение зависит от режима активации плагина (CoverageModeenum CoverageModeОпределяет, нужно ли собирать покрытие.).

Настройка

Зарегистрируйте CodecovPluginnew CodecovPlugin(CoverageLevel $level = CoverageLevel::Line, CoverageMode $collect = CoverageMode::IfAvailable, array $testTypes = [TestType::Test, TestType::TestInline], array $reports = [])Настраивает сбор покрытия кода: глубину анализа, режим активации и формат отчётов. в секции plugins конфигурации:

php
return new ApplicationConfig(
    src: ['src'],
    //...
    plugins: [
        new CodecovPlugin(
            level: CoverageLevel::Line,
            reports: [
                new CloverReport(__DIR__ . '/clover.xml', 'MyProject'),
                new CoberturaReport(__DIR__ . '/cobertura.xml'),
            ],
        ),
    ],
);
php
return new ApplicationConfig(
    src: ['src'],
    suites: [
        new SuiteConfig(
            // ...
            plugins: [
                new CodecovPlugin(
                    reports: [
                        new CloverReport(__DIR__ . '/clover.xml', 'MyProject'),
                    ],
                ),
            ],
        ),
    ],
);

На уровне приложения покрытие собирается по всем Test Suite. На уровне Test Suite — только для конкретного комплекта. Отчёты генерируются после завершения тестов. Покрытие фильтруется по файлам, соответствующим параметру src из ApplicationConfig\Testo\Application\Config\ApplicationConfig.

CodecovPlugin

Настраивает сбор покрытия кода: глубину анализа, режим активации и формат отчётов.

new CodecovPlugin(CoverageLevel $level = CoverageLevel::Line, CoverageMode $collect = CoverageMode::IfAvailable, array $testTypes = [TestType::Test, TestType::TestInline], array $reports = [])

Параметры:

$level
Глубина анализа покрытия. По умолчанию CoverageLevel::Lineenum CoverageLevelОпределяет глубину анализа покрытия. Каждый следующий уровень включает данные предыдущего.LineКакие строки исходного кода были выполнены. Поддерживается PCOV и XDebug..
$collect
Режим активации по умолчанию. CLI-флаги (--coverage, --no-coverage) имеют приоритет над этим значением.
$testTypes
Типы тестов, для которых собирать покрытие. Сбор покрытия добавляет накладные расходы к каждому запуску, поэтому для бенчмарков он по умолчанию отключён — иначе замеры производительности будут искажены. По умолчанию покрытие собирается только для обычных тестов и inline-тестов (TestType::Test\Testo\Core\Value\TestType::Test, TestType::TestInline\Testo\Core\Value\TestType::TestInline). Пустой массив означает все типы. Принимает кейсы TestType\Testo\Core\Value\TestType или произвольные строковые идентификаторы.
$reports
Генераторы отчётов, которые будут выполнены после завершения всех тестов. Каждый элемент должен реализовывать интерфейс CoverageReport\Testo\Codecov\Report\CoverageReport.

CoverageLevel

Определяет глубину анализа покрытия. Каждый следующий уровень включает данные предыдущего.

enum CoverageLevel

Каждый следующий уровень увеличивает накладные расходы на анализ. PCOV поддерживает только Line — при запросе более глубокого уровня он молча возвращается к Line.

Пример. Допустим, есть такой код:

php
function greet(bool $loud, bool $formal): string
{
    $greeting = $formal ? 'Good day' : 'Hi';         // 2 ветви
    return $loud ? strtoupper($greeting) : $greeting; // 2 ветви
}

Тест, вызывающий greet(true, true):

  • Line — 100%: обе строки выполнены.
  • Branch — 50%: пройдены 2 из 4 ветвей.
  • Path — 25%: пройден 1 из 4 путей (true+true, true+false, false+true, false+false).

Значения:

Line
Какие строки исходного кода были выполнены. Поддерживается PCOV и XDebug.
Branch
Line + какие ветви (if/else, switch, ?:, ??) были пройдены. Только XDebug.
Path
Branch + какие полные пути выполнения через каждую функцию были пройдены. Только XDebug.

CoverageMode

Определяет, нужно ли собирать покрытие.

enum CoverageMode

Поведение по умолчанию задаётся параметром collect конструктора CodecovPluginnew CodecovPlugin(CoverageLevel $level = CoverageLevel::Line, CoverageMode $collect = CoverageMode::IfAvailable, array $testTypes = [TestType::Test, TestType::TestInline], array $reports = [])Настраивает сбор покрытия кода: глубину анализа, режим активации и формат отчётов., а CLI-флаги при запуске могут его переопределить. Благодаря этому плагин можно безопасно оставить в testo.php на всех окружениях — на CI без PCOV/XDebug тесты пройдут нормально, просто без отчётов.

Значения:

IfAvailable
По умолчанию. Покрытие собирается, если расширение доступно и настроено, иначе молча пропускается.
Always
Покрытие обязательно. Если расширение не установлено, тесты упадут с исключением CoverageDriverNotAvailable\Testo\Codecov\Exception\CoverageDriverNotAvailable. Устанавливается CLI-флагом --coverage.
Never
Покрытие полностью отключено, нулевые накладные расходы. Устанавливается CLI-флагом --no-coverage.

Отчёты

Все встроенные генераторы отчётов реализуют интерфейс CoverageReport\Testo\Codecov\Report\CoverageReport. Вы можете реализовать его, чтобы добавить собственный формат вывода.

CloverReport

Генерирует отчёт в формате Clover XML.

new CloverReport(string $outputPath, string $projectName = '')

Формат содержит элементы <file>, <line> и <metrics> — только построчное покрытие операторов. Данные о ветвях и путях не включаются, поскольку формат их не поддерживает.

Совместим с: Codecov.io, SonarQube, Atlassian Clover.

Параметры:

$outputPath
Абсолютный путь к выходному XML-файлу.
$projectName
Имя проекта в элементе <project>. По умолчанию пустая строка.

Примеры:

php
new CloverReport(__DIR__ . '/clover.xml', 'MyProject')

CoberturaReport

Генерирует отчёт в формате Cobertura XML.

new CoberturaReport(string $outputPath, string $sourceRoot = '')

Файлы группируются в элементы <package> по директориям, с относительными путями от sourceRoot.

Если доступны данные о ветвях (CoverageLevel::Branchenum CoverageLevelОпределяет глубину анализа покрытия. Каждый следующий уровень включает данные предыдущего.BranchLine + какие ветви (if/else, switch, ?:, ??) были пройдены. Только XDebug. или выше):

  • branch-rate, branches-covered, branches-valid заполняются на всех уровнях (coverage, package, class).
  • Строки с точками ветвления получают атрибуты branch="true" и condition-coverage="50% (1/2)".

Без данных о ветвях все branch-атрибуты равны 0.

Совместим с: GitHub Actions, GitLab CI, Jenkins.

Параметры:

$outputPath
Абсолютный путь к выходному XML-файлу.
$sourceRoot
Корень исходников для формирования относительных путей. По умолчанию getcwd().

Примеры:

php
new CoberturaReport(__DIR__ . '/cobertura.xml')

Управление покрытием

Параметр src в конфигурации ApplicationConfig\Testo\Application\Config\ApplicationConfig определяет глобальный набор файлов, попадающих в покрытие. Атрибуты #[Covers]#[Covers(string $classOrFunction, ?string $method = null)]Ограничивает, какой исходный код засчитывается в покрытие для данного теста. и #[CoversNothing]#[CoversNothing]Исключает тест из статистики покрытия. позволяют точечно управлять покрытием для конкретного теста.

Глобальный фильтр

Поддерживаются включения и исключения через FinderConfig\Testo\Application\Config\FinderConfig:

php
return new ApplicationConfig(
    src: new FinderConfig(
        include: ['src'],
        exclude: ['src/Generated'],
    ),
    // ...
);

Старайтесь включать в src только нужные директории, чтобы отрезать ненужные файлы ещё до их загрузки. Это даёт наибольшую производительность.

#[Covers]

Ограничивает, какой исходный код засчитывается в покрытие для данного теста.

#[Covers(string $classOrFunction, ?string $method = null)]

В отчёт попадут только строки, принадлежащие указанным классам, трейтам, enum-ам, методам или функциям. Всё остальное отбрасывается. Атрибут можно повторять: несколько #[Covers]#[Covers(string $classOrFunction, ?string $method = null)]Ограничивает, какой исходный код засчитывается в покрытие для данного теста. на одном тесте объединяются.

При размещении на классе применяется ко всем тестам внутри.

Параметры:

$classOrFunction
Полное имя класса, трейта, enum-а (UserService::class, Cacheable::class, OrderStatus::class) или функции ('App\Helpers\format_name').
$method
Имя метода внутри класса, трейта или enum-а. Если указано, в покрытие попадут только строки этого метода, а не всей сущности.

Примеры:

Покрытие для класса, трейта или enum-а — все исполняемые строки:

php
#[Covers(UserService::class)]
public function testCreateUser(): void { ... }

#[Covers(Cacheable::class)]
public function testCacheableBehavior(): void { ... }

#[Covers(OrderStatus::class)]
public function testOrderStatusTransitions(): void { ... }

Покрытие для конкретного метода — работает с классами, трейтами и enum-ами:

php
#[Covers(UserService::class, 'create')]
public function testCreateUser(): void { ... }

#[Covers(Cacheable::class, 'invalidate')]
public function testCacheInvalidation(): void { ... }

#[Covers(OrderStatus::class, 'canTransitionTo')]
public function testStatusTransition(): void { ... }

Несколько целей — покрытие объединяется:

php
#[Covers(UserService::class)]
#[Covers(UserRepository::class, 'findById')]
public function testCreateUser(): void { ... }

#[CoversNothing]

Исключает тест из статистики покрытия.

#[CoversNothing]

Тест с этим атрибутом выполняется как обычно, но драйвер покрытия не запускается — нулевые накладные расходы, данные не собираются и не попадают в отчёты. Полезно для smoke-тестов и интеграционных проверок, которые затрагивают много кода, но не должны искажать картину покрытия.

При размещении на классе применяется ко всем тестам внутри.

Примеры:

php
#[CoversNothing]
public function smokeTest(): void
{
    // Тест выполнится, но покрытие не собирается
    $response = $this->app->get('/health');
    Assert::same(200, $response->statusCode);
}

Приоритет атрибутов

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

php
#[CoversNothing]
abstract class BaseIntegrationTest
{
    // По умолчанию все тесты в наследниках не собирают покрытие
}

#[Covers(PaymentService::class)]
final class PaymentServiceTest extends BaseIntegrationTest
{
    // Этот класс переопределяет поведение — покрытие собирается
    public function testCharge(): void { ... }
}

Метаданные

Данные о покрытии каждого теста попадают в метаданные TestResult\Testo\Core\Context\TestResult под ключом CoverageResult\Testo\Codecov\Result\CoverageResult:

php
use Testo\Codecov\Result\CoverageResult;

$coverage = $testResult->getAttribute(CoverageResult::class);
// CoverageResult|null

Таким образом, вы можете реализовать любую логику на основе покрытия до того, как результаты будут отражены в отчётах.

Какое расширение лучше — PCOV или XDebug?

PCOV быстрее и проще в настройке, но поддерживает только построчное покрытие. XDebug нужен для анализа ветвей и путей. Если вам достаточно CoverageLevel::Lineenum CoverageLevelОпределяет глубину анализа покрытия. Каждый следующий уровень включает данные предыдущего.LineКакие строки исходного кода были выполнены. Поддерживается PCOV и XDebug. — используйте PCOV.

Что будет, если не установлено ни одно расширение для покрытия?