Покрытие кода
Плагин собирает данные о покрытии кода во время выполнения тестов и генерирует отчёты в стандартных форматах. Отчёты можно использовать в CI-сервисах (Codecov.io, SonarQube, GitHub Actions) и в IDE — например, PhpStorm умеет отображать покрытие прямо в коде по Clover-отчёту.
Класс плагина: CodecovPluginnew CodecovPlugin(CoverageLevel $level = CoverageLevel::Line, CoverageMode $collect = CoverageMode::IfAvailable, array $testTypes = [TestType::Test, TestType::TestInline], array $reports = [])Настраивает сбор покрытия кода: глубину анализа, режим активации и формат отчётов.. Не входит в состав плагинов по умолчанию.
Требования
Для сбора покрытия нужно одно из 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 конфигурации:
return new ApplicationConfig(
src: ['src'],
//...
plugins: [
new CodecovPlugin(
level: CoverageLevel::Line,
reports: [
new CloverReport(__DIR__ . '/clover.xml', 'MyProject'),
new CoberturaReport(__DIR__ . '/cobertura.xml'),
],
),
],
);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::Line
enum 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.
Пример. Допустим, есть такой код:
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>. По умолчанию пустая строка.
Примеры:
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().
Примеры:
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:
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-а — все исполняемые строки:
#[Covers(UserService::class)]
public function testCreateUser(): void { ... }
#[Covers(Cacheable::class)]
public function testCacheableBehavior(): void { ... }
#[Covers(OrderStatus::class)]
public function testOrderStatusTransitions(): void { ... }Покрытие для конкретного метода — работает с классами, трейтами и enum-ами:
#[Covers(UserService::class, 'create')]
public function testCreateUser(): void { ... }
#[Covers(Cacheable::class, 'invalidate')]
public function testCacheInvalidation(): void { ... }
#[Covers(OrderStatus::class, 'canTransitionTo')]
public function testStatusTransition(): void { ... }Несколько целей — покрытие объединяется:
#[Covers(UserService::class)]
#[Covers(UserRepository::class, 'findById')]
public function testCreateUser(): void { ... }#[CoversNothing]
Исключает тест из статистики покрытия.
#[CoversNothing]Тест с этим атрибутом выполняется как обычно, но драйвер покрытия не запускается — нулевые накладные расходы, данные не собираются и не попадают в отчёты. Полезно для smoke-тестов и интеграционных проверок, которые затрагивают много кода, но не должны искажать картину покрытия.
При размещении на классе применяется ко всем тестам внутри.
Примеры:
#[CoversNothing]
public function smokeTest(): void
{
// Тест выполнится, но покрытие не собирается
$response = $this->app->get('/health');
Assert::same(200, $response->statusCode);
}Приоритет атрибутов
Атрибуты покрытия разрешаются послойно: сначала проверяется метод, затем класс. Если на методе есть любой атрибут покрытия, атрибуты класса игнорируются. Это позволяет переопределять поведение в наследниках:
#[CoversNothing]
abstract class BaseIntegrationTest
{
// По умолчанию все тесты в наследниках не собирают покрытие
}
#[Covers(PaymentService::class)]
final class PaymentServiceTest extends BaseIntegrationTest
{
// Этот класс переопределяет поведение — покрытие собирается
public function testCharge(): void { ... }
}Использование #[Covers]#[Covers(string $classOrFunction, ?string $method = null)]Ограничивает, какой исходный код засчитывается в покрытие для данного теста. и #[CoversNothing]#[CoversNothing]Исключает тест из статистики покрытия. на одном уровне — ошибка. Testo выбросит исключение с указанием конфликтующего теста. Разные уровни (например, #[CoversNothing]#[CoversNothing]Исключает тест из статистики покрытия. на родительском классе и #[Covers]#[Covers(string $classOrFunction, ?string $method = null)]Ограничивает, какой исходный код засчитывается в покрытие для данного теста. на дочернем) — допустимы.
Метаданные
Данные о покрытии каждого теста попадают в метаданные TestResult\Testo\Core\Context\TestResult под ключом CoverageResult\Testo\Codecov\Result\CoverageResult:
use Testo\Codecov\Result\CoverageResult;
$coverage = $testResult->getAttribute(CoverageResult::class);
// CoverageResult|nullТаким образом, вы можете реализовать любую логику на основе покрытия до того, как результаты будут отражены в отчётах.
Какое расширение лучше — PCOV или XDebug?
PCOV быстрее и проще в настройке, но поддерживает только построчное покрытие. XDebug нужен для анализа ветвей и путей. Если вам достаточно CoverageLevel::Lineenum CoverageLevelОпределяет глубину анализа покрытия. Каждый следующий уровень включает данные предыдущего.LineКакие строки исходного кода были выполнены. Поддерживается PCOV и XDebug. — используйте PCOV.
Что будет, если не установлено ни одно расширение для покрытия?
Зависит от режима активации. По умолчанию используется CoverageMode::IfAvailableenum CoverageModeОпределяет, нужно ли собирать покрытие.IfAvailableПо умолчанию. Покрытие собирается, если расширение доступно и настроено, иначе молча пропускается. — плагин молча пропустит сбор покрытия, и тесты выполнятся без него. Если запустить с флагом --coverage, режим установится в CoverageMode::Alwaysenum CoverageModeОпределяет, нужно ли собирать покрытие.AlwaysПокрытие обязательно. Если расширение не установлено, тесты упадут с исключением CoverageDriverNotAvailable\Testo\Codecov\Exception\CoverageDriverNotAvailable. Устанавливается CLI-флагом --coverage., и тесты упадут с исключением CoverageDriverNotAvailable\Testo\Codecov\Exception\CoverageDriverNotAvailable.