Используйте код покрытия для модульного тестирования

Внимание

В этой статье объясняется, как создать пример проекта. Если у вас уже есть проект, можно перейти к разделу Средства для проверки объема протестированного кода.

Модульные тесты помогают подтвердить наличие нужной функциональности и предоставляют средства проверки действий по рефакторингу. Объем протестированного кода — это измерение объема кода, выполняемого модульными тестами (строки, ветви или методы). Например, если у вас есть простое приложение с двумя условными ветвями кода (ветвь a и ветвь b), модульный тест, проверяющий условную ветвь a сообщит об объеме протестированного кода ветви в 50 %.

В этой статье рассматривается использование объема протестированного кода для модульного тестирования с использованием Coverlet и создания отчетов с помощью ReportGenerator. Несмотря на то что эта статья посвящена C# и xUnit в качестве платформы тестирования, MSTest и NUnit также будут работать. Coverlet — это проект с открытым исходным кодом на GitHub, который предоставляет кроссплатформенную платформу покрытия кода для C#. Coverlet является частью .NET Foundation. Coverlet собирает данные тестового запуска покрытия Cobertura, которые используются для создания отчета.

Кроме того, в этой статье подробно описано, как использовать сведения об объеме протестированного кода, собранные из тестового запуска Coverlet, для создания отчета. Создание отчетов возможно с помощью другого проекта с открытым исходным кодом на сайте GitHub — ReportGenerator. ReportGenerator преобразует отчеты о покрытии, созданные Cobertura и многими другими, в читаемые человеком отчеты в различных форматах.

В основе этой статьи лежит образец проекта исходного кода, доступный в обозревателе примеров.

Тестируемая система

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

Создание библиотеки классов

В командной строке в новом каталоге с именем UnitTestingCodeCoverage создайте новую стандартную библиотеку классов .NET с помощью команды dotnet new classlib.

dotnet new classlib -n Numbers

Приведенный ниже фрагмент кода определяет простой класс PrimeService, который предоставляет функциональные возможности для проверки того, является ли число простым. Скопируйте приведенный ниже фрагмент кода и замените содержимое файла Class1.cs, который был автоматически создан в каталоге Numbers. Переименуйте файл Class1.cs в PrimeService.cs.

namespace System.Numbers
{
    public class PrimeService
    {
        public bool IsPrime(int candidate)
        {
            if (candidate < 2)
            {
                return false;
            }

            for (int divisor = 2; divisor <= Math.Sqrt(candidate); ++divisor)
            {
                if (candidate % divisor == 0)
                {
                    return false;
                }
            }
            return true;
        }
    }
}

Совет

Стоит упомянуть, что библиотека классов Numbers была намеренно добавлена в пространство имен System. Это позволяет получить доступ к System.Math без объявления пространства имен using System;. Дополнительные сведения см. в разделе Пространство имен (справка по C#).

Создание тестовых проектов

Создайте два новых шаблона тестового проекта xUnit (.NET Core) из одной командной строки с помощью команды dotnet new xunit:

dotnet new xunit -n XUnit.Coverlet.Collector
dotnet new xunit -n XUnit.Coverlet.MSBuild

Оба новых созданных тестовых проекта xUnit должны добавить ссылку на проект библиотеки классов Numbers. Это значит, что тестовые проекты имеют доступ к PrimeService для тестирования. В командной строке используйте команду dotnet add:

dotnet add XUnit.Coverlet.Collector\XUnit.Coverlet.Collector.csproj reference Numbers\Numbers.csproj
dotnet add XUnit.Coverlet.MSBuild\XUnit.Coverlet.MSBuild.csproj reference Numbers\Numbers.csproj

Проект MSBuild назван соответствующим образом, так как он будет зависеть от пакета NuGet coverlet.msbuild. Добавьте эту зависимость пакета, выполнив dotnet package add команду. (Если вы используете версию SDK .NET 9 или более раннюю, используйте форму dotnet add package.)

cd XUnit.Coverlet.MSBuild && dotnet package add coverlet.msbuild && cd ..

Предыдущая команда изменила каталоги в области действия на тестовый проект MSBuild, а затем добавила пакет NuGet. После этого были изменены каталоги, и выполнен шаг с заходом на один уровень вверх.

Откройте оба файла UnitTest1.cs и замените их содержимое следующим фрагментом кода. Переименуйте файлы UnitTest1.cs в PrimeServiceTests.cs.

using System.Numbers;
using Xunit;

namespace XUnit.Coverlet
{
    public class PrimeServiceTests
    {
        readonly PrimeService _primeService;

        public PrimeServiceTests() => _primeService = new PrimeService();

        [Theory]
        [InlineData(-1), InlineData(0), InlineData(1)]
        public void IsPrime_ValuesLessThan2_ReturnFalse(int value) =>
            Assert.False(_primeService.IsPrime(value), $"{value} shouldn't be prime");

        [Theory]
        [InlineData(2), InlineData(3), InlineData(5), InlineData(7)]
        public void IsPrime_PrimesLessThan10_ReturnTrue(int value) =>
            Assert.True(_primeService.IsPrime(value), $"{value} should be prime");

        [Theory]
        [InlineData(4), InlineData(6), InlineData(8), InlineData(9)]
        public void IsPrime_NonPrimesLessThan10_ReturnFalse(int value) =>
            Assert.False(_primeService.IsPrime(value), $"{value} shouldn't be prime");
    }
}

Создание решения

В командной строке создайте новое решение для инкапсуляции библиотеки классов и двух тестовых проектов. С помощью команды dotnet sln:

dotnet new sln -n XUnit.Coverage

При этом создается новое имя XUnit.Coverage файла решения в каталоге UnitTestingCodeCoverage . Добавьте проекты в корневую папку решения.

dotnet sln XUnit.Coverage.sln add **/*.csproj --in-root

Постройте решение, используя команду dotnet build.

dotnet build XUnit.Coverage.sln

Если сборка выполнена успешно, это означает, что вы создали три проекта, соответствующие проекты и пакеты и обновили исходный код правильно. Отличная работа!

Средства для проверки объема протестированного кода

Существует два типа инструментов для анализа покрытия кода:

  • DataCollectors: DataCollectors отслеживает выполнение теста и собирает сведения о тестовых запусках. Они представляют собранные сведения в различных форматах вывода, таких как XML и JSON. Дополнительные сведения см. в статье Создание своего первого сборщика данных.
  • Генераторы отчетов: используйте данные, собранные из тестовых запусков, для создания отчетов часто в стиле HTML.

В этом разделе основное внимание уделяется инструментам для сбора данных.

Подходы к охвату кода

.NET предоставляет две разные платформы тестирования, каждая из которых имеет собственный подход к охвату кода:

Встроенный сборщик данных покрытия кода .NET создает двоичный файл .coverage, который можно использовать для создания отчетов в Visual Studio. Двоичный файл не является удобочитаемым, и его необходимо преобразовать в удобочитаемый для человека формат, прежде чем его можно будет использовать для создания отчетов за пределами Visual Studio.

Совет

Это dotnet-coverage кроссплатформенное средство, которое можно использовать для преобразования файла результатов тестирования двоичного покрытия в формат, доступный для чтения. Дополнительные сведения см. в разделе dotnet-coverage.

Coverlet создает результаты тестов в виде XML-файлов Cobertura, удобных для чтения человеком, которые затем можно использовать для создания HTML-отчетов. Чтобы использовать Coverlet для проверки объема протестированного кода, существующий проект модульного теста должен иметь соответствующие зависимости пакета или полагаться на глобальный инструментарий .NET, а также на соответствующий пакет NuGet coverlet.console.

Покрытие кода с помощью VSTest

VSTest — это платформа тестирования, используемая MSTest, NUnit и xUnit. Для проектов на основе VSTest можно использовать coverlet или встроенный сборщик покрытия кода .NET.

Использование Coverlet с VSTest

Шаблон тестового проекта xUnit по умолчанию интегрируется с coverlet.collector. В командной строке перейдите в каталог проекта XUnit.Coverlet.Collector и выполните команду dotnet test:

cd XUnit.Coverlet.Collector && dotnet test --collect:"XPlat Code Coverage"

Примечание.

Аргумент "XPlat Code Coverage" — это понятное имя, соответствующее сборщикам данных из Coverlet. Это имя обязательно, регистр не имеет значения. Чтобы использовать встроенный сборщик данных покрытия кода .NET, используйте "Code Coverage".

В рамках запуска dotnet test полученный файл coverage.cobertura.xml выводится в каталог TestResults. XML-файл содержит результаты. Это кроссплатформенный вариант, основанный на .NET CLI, и он отлично подходит для систем сборки, где MSBuild недоступен.

Ниже приведен пример файла coverage.cobertura.xml.

<?xml version="1.0" encoding="utf-8"?>
<coverage line-rate="1" branch-rate="1" version="1.9" timestamp="1592248008"
          lines-covered="12" lines-valid="12" branches-covered="6" branches-valid="6">
  <sources>
    <source>C:\</source>
  </sources>
  <packages>
    <package name="Numbers" line-rate="1" branch-rate="1" complexity="6">
      <classes>
        <class name="Numbers.PrimeService" line-rate="1" branch-rate="1" complexity="6"
               filename="Numbers\PrimeService.cs">
          <methods>
            <method name="IsPrime" signature="(System.Int32)" line-rate="1"
                    branch-rate="1" complexity="6">
              <lines>
                <line number="8" hits="11" branch="False" />
                <line number="9" hits="11" branch="True" condition-coverage="100% (2/2)">
                  <conditions>
                    <condition number="7" type="jump" coverage="100%" />
                  </conditions>
                </line>
                <line number="10" hits="3" branch="False" />
                <line number="11" hits="3" branch="False" />
                <line number="14" hits="22" branch="True" condition-coverage="100% (2/2)">
                  <conditions>
                    <condition number="57" type="jump" coverage="100%" />
                  </conditions>
                </line>
                <line number="15" hits="7" branch="False" />
                <line number="16" hits="7" branch="True" condition-coverage="100% (2/2)">
                  <conditions>
                    <condition number="27" type="jump" coverage="100%" />
                  </conditions>
                </line>
                <line number="17" hits="4" branch="False" />
                <line number="18" hits="4" branch="False" />
                <line number="20" hits="3" branch="False" />
                <line number="21" hits="4" branch="False" />
                <line number="23" hits="11" branch="False" />
              </lines>
            </method>
          </methods>
          <lines>
            <line number="8" hits="11" branch="False" />
            <line number="9" hits="11" branch="True" condition-coverage="100% (2/2)">
              <conditions>
                <condition number="7" type="jump" coverage="100%" />
              </conditions>
            </line>
            <line number="10" hits="3" branch="False" />
            <line number="11" hits="3" branch="False" />
            <line number="14" hits="22" branch="True" condition-coverage="100% (2/2)">
              <conditions>
                <condition number="57" type="jump" coverage="100%" />
              </conditions>
            </line>
            <line number="15" hits="7" branch="False" />
            <line number="16" hits="7" branch="True" condition-coverage="100% (2/2)">
              <conditions>
                <condition number="27" type="jump" coverage="100%" />
              </conditions>
            </line>
            <line number="17" hits="4" branch="False" />
            <line number="18" hits="4" branch="False" />
            <line number="20" hits="3" branch="False" />
            <line number="21" hits="4" branch="False" />
            <line number="23" hits="11" branch="False" />
          </lines>
        </class>
      </classes>
    </package>
  </packages>
</coverage>

Совет

В качестве альтернативы можно использовать пакет MSBuild, если система сборки уже использует MSBuild. В командной строке перейдите в каталог проекта XUnit.Coverlet.MSBuild и выполните команду dotnet test:

dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura

Полученный файл coverage.cobertura.xml выводится. Дополнительные сведения см. в разделе "Интеграция Coverlet с MSBuild"

Создание отчетов

Теперь, когда настроено получение данных из запусков модульного теста, можно создавать отчеты с помощью ReportGenerator. Чтобы установить пакет NuGet ReportGenerator в качестве глобального инструмента .NET, используйте команду dotnet tool install:

dotnet tool install -g dotnet-reportgenerator-globaltool

Запустите средство и укажите нужные параметры, используя файл выходных данных coverage.cobertura.xml из предыдущего тестового запуска.

reportgenerator -reports:"Path\To\TestProject\TestResults\{guid}\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html

После выполнения этой команды созданный отчет будет представлен в HTML-файле.

Отчет, сформированный модульным тестом

Покрытие кода с Microsoft.Testing.Platform

Microsoft.Testing.Platform (MTP) — это упрощенная и переносимая альтернатива VSTest для выполнения тестов. MSTest, NUnit и xUnit.net поддерживают MTP через их средства выполнения, а TUnit поддерживает только MTP. Проекты, работающие на MTP, собирают покрытие кода с помощью расширения MTP вместо сборщика данных VSTest. В этом разделе используется расширение покрытия кода Microsoft. Сведения о Coverlet см. в разделе Покрытие кода.

Добавить расширение для покрытия кода

Добавьте пакет Microsoft.Testing.Extensions.CodeCoverage в тестовый проект:

dotnet add package Microsoft.Testing.Extensions.CodeCoverage

Примечание.

Пакет TUnit уже ссылается на Microsoft.Testing.Extensions.CodeCoverage, поэтому проекты TUnit могут пропустить этот шаг.

Включите режим MTP dotnet test

Этот параметр --coverage является параметром MTP, поэтому dotnet test должен работать в режиме MTP. С помощью пакета SDK .NET 10 или более поздней версии добавьте следующую конфигурацию в файл global.json в корневом каталоге решения:

{
    "test": {
        "runner": "Microsoft.Testing.Platform"
    }
}

Эта конфигурация применяется к каждому фреймворку на основе MTP, а не только к TUnit. Без него dotnet test выполняется в режиме VSTest и не распознает --coverage Дополнительные сведения, включая запуск проектов MTP с более ранними пакетами SDK, см. в разделе Тестирование с помощью dotnet test.

Сбор данных о покрытии кода

Выполните тесты с параметром --coverage :

dotnet test --coverage

Расширение записывает двоичный файл .coverage в каталог TestResults. Чтобы создать другой формат, используйте параметр --coverage-output-format. Поддерживаемые значения: coverage (по умолчанию), xml и cobertura. Например, следующая команда создает XML-файл Cobertura, который можно передать в ReportGenerator, как описано в разделе "Создание отчетов".

dotnet test --coverage --coverage-output-format cobertura

Сведения обо всех параметрах см. в разделе Покрытие кода.

См. также

Следующие шаги