将代码覆盖率用于单元测试
重要
本文介绍如何创建示例项目。 如果已有项目,可以直接跳到代码覆盖率工具部分。
单元测试有助于确保功能的正常运行,并为重构工作提供一种验证方法。 代码覆盖率是单元测试运行的代码量(行、分支或方法)的度量值。 例如,如果你有一个简单的应用程序,其中只有两个条件分支(分支 a 和分支 b ),则验证条件分支 a 的单元测试将报告 50% 的分支代码覆盖率。
本文介绍如何通过 Coverlet 在单元测试中使用代码覆盖率和使用 ReportGenerator 生成报表。 尽管本文重点介绍 C# 和 xUnit 作为测试框架,但 MSTest 和 NUnit 也适用。 Coverlet 是 GitHub 上的开源项目,可为 C# 提供跨平台代码覆盖率框架。 Coverlet 是 .NET Foundation 的一部分。 Coverlet 收集 Cobertura 覆盖率测试运行数据,用于生成报表。
此外,本文详细介绍如何使用从 Coverlet 测试运行收集的代码覆盖率信息来生成报表。 可以使用另一个 GitHub 上的开源项目 - ReportGenerator 来生成报表。 ReportGenerator 将由 Cobertura 生成的覆盖率报表转换为各种格式的用户可读的报表。
本文基于示例浏览器中提供的示例源代码项目。
测试中的系统
“测试中的系统”指的是要对其编写单元测试的代码,这可能是对象、服务或其他任何公开可测试功能的内容。 在本文中,你将创建一个类库(它将成为测试中的系统),以及两个对应的单元测试项目。
创建类库
在名为 UnitTestingCodeCoverage
的新目录中的命令提示符下,使用 dotnet new classlib
命令创建新的 .NET 标准类库:
dotnet new classlib -n Numbers
下面的代码段定义了一个简单的 PrimeService
类,该类提供了用于检查数值是否为质数的功能。 复制下面的代码片段,并替换在“编号”目录中自动创建的“Class1.cs”文件的内容 。 将“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
命名空间中的。 因此,无需 using System;
命名空间声明即可访问 System.Math。 有关详细信息,请参阅命名空间(C# 参考)。
创建测试项目
使用 dotnet new xunit
命令,在同一命令提示符下创建两个新的“xUnit 测试项目(.NET Core)”模板:
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 项目命名正确,因为它依赖于 coverlet.msbuild NuGet 包。 通过运行 dotnet add package
命令添加此包依赖项:
cd XUnit.Coverlet.MSBuild && dotnet add package 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} should not 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} should not be prime");
}
}
创建解决方案
在命令提示符下,创建一个用于封装类库和两个测试项目的新解决方案。 使用 dotnet sln
命令:
dotnet new sln -n XUnit.Coverage
这会在 UnitTestingCodeCoverage 目录中创建新的解决方案文件名 XUnit.Coverage
。 将项目添加到解决方案的根。
使用 dotnet build
命令生成解决方案:
dotnet build
如果生成成功,则已创建了三个项目,正确引用了项目和包,并正确更新了源代码。 做得不错!
代码覆盖率工具
代码覆盖率工具有两种类型:
- 数据收集器: 数据收集器监视测试执行并收集有关测试运行的信息。 它们以各种输出格式(例如 XML 和 JSON)报告收集的信息。 有关详细信息,请参阅第一个数据收集器。
- 报表生成器: 使用从测试运行收集的数据生成报表,通常为带样式的 HTML。
本部分重点介绍数据收集器工具。
.NET 包括内置代码覆盖率数据收集器,Visual Studio 中也有提供。 此数据收集器生成二进制 .coverage 文件,该文件可用于在 Visual Studio 中生成报表。 二进制文件并非人工可读,且必须转换为人工可读格式,然后才能使用它在 Visual Studio 外部生成报表。
提示
dotnet-coverage
工具是一种跨平台工具,可用于将二进制覆盖率测试结果文件转换为人工可读格式。 有关详细信息,请参阅 dotnet-counters。
Coverlet 是内置收集器的开源替代方法。 它将测试结果生成为人工可读的 Cobertura XML 文件,然后可用于生成 HTML 报告。 若要通过 Coverlet 获得代码覆盖率,现有单元测试项目必须具有相应的包依赖项,或者依赖于 .NET 全局工具和对应的 coverlet.console NuGet 包。
与 .NET 测试集成
默认情况下,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 文件为输出。 可按照此处的 MSBuild 集成指南操作
生成报告
现在,你既可从单元测试运行收集数据,就可以使用 ReportGenerator 来生成报表。 若要将 ReportGenerator NuGet 包安装为 .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 文件表示生成的报表。
另请参阅
- Visual Studio 单元测试代码覆盖率
- GitHub - Coverlet 存储库
- GitHub - ReportGenerator 存储库
- ReportGenerator 项目网站
- Azure:发布代码覆盖率结果
- Azure:查看代码覆盖率结果
- .NET CLI 测试命令
- dotnet-coverage
- 示例源代码