引言

在现代化的软件开发中,单元测试集成测试是确保代码质量和可靠性的关键部分。ASP.NET Core 社区内提供了强大的单元测试框架,xUnit 是其中之一,它提供了简单、清晰和强大的测试功能,编写单元测试有许多优点;有助于回归、提供文档及辅助良好的设计。下面几节我们来深入浅出探讨如何使用 xUnit 进行 ASP.NET Core 应用程序的单元测试和集成测试。

内容大纲:

xUnit 简介

xUnit.net 是一个免费、开源、面向社区的.NET 单元测试工具。由 NUnit v2 的原始发明者编写,xUnit.net 是用于 C#F#(其他.NET 语言可能也可以使用,但不受支持)的最新技术单元测试。xUnit.net 可与 Visual StudioVisual Studio CodeReSharperCodeRushTestDriven.NET 一起使用。它是.NET 基金会的一部分,并遵守其行为准则。其许可协议为 Apache 2(为 OSI 批准的许可协议)。

xUnit.net 官方网站

创建单元测试项目

在单元测试中通常要遵循AAA模式,也就是 ArrangeActAssert,这是一种常见的测试组织结构。

  • Arrange(准备): 在这个阶段,将设置测试的前提条件,初始化对象、设置输入参数等。简单讲就是准备测试环境,确保被测代码在正确的上下文中执行。
  • Act(执行): 在这个阶段,会执行要测试的代码或方法。这是针对被测代码的实际调用或操作。
  • Assert: 在这个阶段,会验证被测代码的行为是否符合预期。检查实际结果与期望结果是否一致,如果不一致则测试失败。

示例:

[Fact]
public void Add_EmptyString_ReturnsZero()
{
// Arrange
var stringCalculator = new StringCalculator(); // Act
var actual = stringCalculator.Add(""); // Assert
Assert.Equal(0, actual);
}

可读性是编写单元测试最重要的方面之一,在测试中分离这些操作 都明确地突出调用代码所需的依赖项、调用代码的方式以及尝试断言的内容,让测试尽可能具有可读性。

好了理解了这个核心概念我们可以先创建项目一步步的练习了。

用 VS 创建单元测试项目

在项目创建完之后我们可以简单浏览一下 xUnit 单元测试项目装了那些 nuget 依赖,做到对项目有个简单的了解

  <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

下面我们创建一个简单的数据计算类。

  • 创建数学计算类
public class MathCalculator
{
public int Add(int a, int b)
{
return a + b;
}
}
  • 创建数据计算测试类
public class MathCalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnSum()
{
// Arrange
var calculator = new MathCalculator(); // Act
var result = calculator.Add(3, 5); // Assert
Assert.Equal(8, result);
}
}

测试一下,测试类库右键->运行测试

可以看到 我们的单元测试通过。

单元测试命名规范

本着代码自文档的原则,测试的名称建议应包括三个部分:

  • 要测试的方法的名称。
  • 测试的方案。
  • 调用方案时的预期行为。

示例

    [Fact]
public void Add_TwoNumbers_ReturnSum()
{
// Arrange
var calculator = new MathCalculator(); // Act
var result = calculator.Add(3, 5); // Assert
Assert.Equal(8, result);
}

要测试的方法名称是 MathCalculator 中的 Add 方法,测试的方案是传两个数,预期是返回两数之和 按照上面的测试名称的命名规则可以命名为Add_TwoNumbers_ReturnSum

单元测试最佳命名规范应该包括三个关键部分:要测试的方法的名称、测试的场景,以及调用该场景时的预期行为。良好的命名标准能清晰表达测试意图,提供有效文档,便于他人理解代码行为和快速定位问题。

单元测试最佳实践


将方法标记为测试方法在 xUnit 中有两个属性,FactTheory

Fact 属性

在方法上我们看到有一个 Attribute [Fact] ,[Fact] 属性是 xUnit 中最基本的测试属性之一,用于标记一个方法作为一个无需参数且不返回任何内容的测试方法。被标记为 [Fact] 的方法将会被 xUnit 框架识别并执行.

Theory 属性

Theory 属性用于标记一个测试方法,该方法可以接受参数并运行多次,每次运行时使用不同的参数值。Theory 属性通常用于数据驱动测试,允许在同一个测试方法中使用不同的输入数据进行测试.

InlineData 属性

[InlineData] 属性指定这些输入 Theory 标记的测试方法的参数值。

示例:

[Theory]
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value); Assert.False(result, $"{value} should not be prime");
}

InlineData 适用于静态、硬编码的测试数据集合,适合于简单且固定的测试场景。

MemberData 属性

MemberData 属性是 xUnit 中用于数据驱动测试的一种方式,它允许从一个字段、属性或方法中获取测试数据,并将这些数据传递给测试方法进行多次测试。通过 MemberData 属性,可以更灵活地管理和提供测试数据,适用于需要动态生成测试数据的情况。

使用方式

  • 标记测试方法:使用 [Theory] 属性标记测试方法,以便接受从 MemberData 属性提供的测试数据。
  • 准备测试数据:创建一个公共静态字段、属性或方法,该字段、属性或方法返回一个 IEnumerable<object[]> 对象,其中每个 object[] 对象代表一组测试数据。
  • 传递测试数据:在 MemberData 属性中指定要使用的数据源,从而将数据传递给测试方法。

示例

    public static IEnumerable<object[]> GetComplexTestData()
{
yield return new object[] { 10, 5, 15 }; // 测试数据 1
yield return new object[] { -3, 7, 4 }; // 测试数据 2
yield return new object[] { 0, 0, 0 }; // 测试数据 3
// 可以根据需要继续添加更多的测试数据
} [Theory]
[MemberData(nameof(GetComplexTestData))]
public void Add_TwoNumbers_ReturnsSumofNumbers01(int first, int second, int sum)
{
// Arrange
var calculator = new MathCalculator(); // Act
var result = calculator.Add(first, second); // Assert
Assert.Equal(sum, result);
}

MemberData 适用于动态、灵活的测试数据集合,适合于需要从外部源动态获取测试数据的情况。

自定义属性

除了上面提到的 InlineDataMemberData 之外还可以有更加灵活的方式继承DataAttribute实现自定义的Attribute

我们来做一个实现和上面一样的需求

  • 实现 Custom Attribute

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CustomDataAttribute : DataAttribute
{ private readonly int _first;
private readonly int _second;
private readonly int _sum; public CustomDataAttribute(int first, int second, int sum)
{
_first = first;
_second = second;
_sum = sum;
}
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
yield return new object[] { _first, _second, _sum };
}
}
  • 用例
   [Theory]
[CustomData(1, 2, 3)]
[CustomData(2, 3, 5)]
public void Add_TwoNumbers_ReturnSum03(int num1, int num2, int expectedSum)
{
// Arrange
var calculator = new MathCalculator(); // Act
var result = calculator.Add(num1, num2); // Assert
Assert.Equal(expectedSum, result);
}

自定义属性相较于使用 InlineDataMemberData 有以下优势:

  1. 灵活性:自定义属性允许您实现更复杂的逻辑来动态生成测试数据,可以从不同数据源中获取数据,实现更灵活的数据驱动测试。

  2. 重用性:通过自定义属性,您可以将相同的测试数据逻辑应用于多个测试方法,提高测试代码的重用性和可维护性。

  3. 可扩展性:自定义属性可以根据需求进行定制和扩展,适应不同的测试场景和数据需求,使得测试数据的生成更具灵活性。

  4. 可读性:通过自定义属性,可以使测试代码更具可读性和表达力,更清晰地表达测试数据的来源和意图。

尽管使用 InlineDataMemberData 可以满足大多数简单的测试数据需求,但当需要更复杂的数据生成逻辑、数据源、或者对测试数据进行处理时,使用自定义属性会更具优势,能够更好地满足个性化的测试需求。

在测试中应避免逻辑

[Theory]的出现就是为了避免我们在单元测试时编写一些额外的逻辑,造成测试之外的一些错误。

编写单元测试时,请避免手动字符串串联、逻辑条件(例如 if、while、for 和 switch)以及其他条件。

错误示范:

   [Fact]
public void Add_TwoNumbers_ReturnsSumofNumbers02()
{
// Arrange
var calculator = new MathCalculator();
var testData = new List<(int, int, int)>
{
(1, 2, 3),
(2, 3, 5),
(3, 4, 7)
}; // Act & Assert
foreach (var (first, second, sum) in testData)
{
var result = calculator.Add(first, second);
Assert.Equal(sum, result);
}
}

此处用了 forEach 循环来批量断言,违反了单元测试的最佳实践。

测试中应避免逻辑的好处是:

  • 降低在测试中引入 bug 的可能性。
  • 专注于最终结果,而不是实现细节。

ITestOutputHelper 控制台输出

在 xUnit 中我们利用 Console.WriteLine输出时发现什么也不会显示,在 xUnit 单元测试项目中我们需要利用ITestOutputHelper

ITestOutputHelper是 xUnit 中的一个接口,用于在单元测试中输出信息。通过 ITestOutputHelper,您可以在测试运行时将调试信息、日志信息等输出到测试结果中,方便调试和查看测试过程中的输出信息。

调试

再要测试的方法上右键选择调试测试,或者点击方法上面的小点

最后

本篇文章简单的讲了单元测试的基础知识,让大家先对单元测试有个基本的概念,这些用在具体的项目中显然是不够的,后面的章节我们聊一下 TDD,Fake 管理,Log 日志输出,单元测试覆盖率,WebApi 的集成测试,DependencyInjection,Bogus,还有 Devops 的单元测试等知识。

本文完整源代码

.Net单元测试xUnit和集成测试指南(1)的更多相关文章

  1. Mokito 单元测试与 Spring-Boot 集成测试

    Mokito 单元测试与 Spring-Boot 集成测试 版本说明 Java:1.8 JUnit:5.x Mokito:3.x H2:1.4.200 spring-boot-starter-test ...

  2. 单元测试-xUnit总结

    xUnit总结 什么是xUnit xUnit.net是针对.NET Framework的免费,开源,以社区为中心的单元测试工具. 自动化测试的优点 可以频繁的进行测试 可以在任何时间进行测试,也可以按 ...

  3. MVC 单元测试xUnit初探

    对于.NET项目 Web Api的业务逻辑后台开发[特别是做Web Api接口]而言,编写单元测试用例,会极大的减轻代码帮助与运行的方式.然而使用测试框架,相对于自带的,我更加推荐是用xUnit.ne ...

  4. ASP.NET Core 入门(3)(单元测试Xunit及Shouldly的使用)

    一.本篇简单介绍下在ASP.NET Core项目如何使用单元测试,例子是使用VS自带的Xunit来测试Web API接口,加上一款开源的断言工具Shouldly,方便写出更简洁.可读行更好的测试代码. ...

  5. .Net core--创建一个单元测试xUnit

    创建一个xUnit项目  webApi.test 创建之后会有一个默认的[Fact]  (测试的标准格式) [Fact] public void TestEqual() { int a = 10, b ...

  6. .NET 程序集单元测试工具 SmokeTest 应用指南

    Smoke Test(冒烟测试),也称Regression Test(回归测试),是对软件的安装和基本功能的测试.一般地我们使用脚本来实现Smoke Test的自动化,可借用虚拟机的snapshot机 ...

  7. 如何在ASP.NET 5和XUnit.NET中进行LocalDB集成测试

    今天继续昨天的话题--单元测试,不过是在ASP.NET 5中的单元测试. 在当前的Visual Studio 2015 CTP6中,MSTest是不支持对ASP.NET 5项目进行单元测试的.因而,要 ...

  8. .NET Core 3.0 单元测试与 Asp.Net Core 3.0 集成测试

    单元测试与集成测试 测试必要性说明 相信大家在看到单元测试与集成测试这个标题时,会有很多感慨,我们无数次的在实践中提到要做单元测试.集成测试,但是大多数项目都没有做或者仅建了项目文件.这里有客观原因, ...

  9. Unity3d官方测试插件学习-单元测试,集成测试

    2016/11/27更新:官方的测试工具有许多问题,我修改了一个版本 https://git.oschina.net/Hont/UnitTest_Modifyed 支持切场景,异常不失败等 其实Uni ...

  10. Tessy — 嵌入式软件单元测试/ 集成测试工具

    Tessy 源自戴姆勒- 奔驰公司的软件技术实验室,由德国Hitex 公司负责全球销售及技术支持服务,是一款专门针对嵌入式软件进行单元/ 集成测试的工具.它可以对C/C++ 代码进行单元.集成测试,可 ...

随机推荐

  1. C++ 多线程的错误和如何避免(3)

    传递给 C++ 线程的构造函数的参数是通过值传递的 VS 平台:2019 问题:如何在线程中改变传递的参数值? 比如: #include <functional> #include < ...

  2. 如何避免Git合并远程分支时出现可读性差的日志

    问题及现象 当某一分支(假设为main)的本地仓库和远程仓库都基于同一个提交进行了修改,并分别创建了新的提交时,在本地执行git push origin main会提示先要执行git pull合并远程 ...

  3. 【Azure Kubernetes】通过 kubelogin 进行非交互式登录AKS

    问题描述 当对AKS的登录方式(认证和授权)从"Local Account with Kubernetes RBAC "改变为"Azure AD authenticati ...

  4. 【Azure 存储服务】关于中国区Azure Storage Account 存储账号服务误删除后的恢复问题

    问题描述 在Azure上,如果需要恢复之前删除的存储账户(Storage Account), 有什么办法呢? 问题解答 Azure 现在推出了自主恢复已删除的存储账号的功能,具体步骤如下: 第一步: ...

  5. 简单配置Sql专家云

    一.实例配置 1.添加实例 点击全面诊断实例配置,右上角点击添加. 2.填写实例信息 根据下图填写对应的信息,连接测试成功后点击保存. 3.添加完成 4.修改实例 找到对应的实例,点击下图蓝色框修改即 ...

  6. Jmeter json断言的使用

    1 添加方式:取样器右键->添加->断言->JSON断言 作用:使用JSON表达式提取实际数据与预期进行比较   2首先我们来了解下断言组件的各个功能: Asset JSON Pat ...

  7. Java 子类对象实例化的全过程

    2 /* 3 * 子类对象实例化的全过程 4 * 5 *1.结果上来看:(继承性) 6 * 子类继承父类以后,就获取了父类中声明的属性或方法 7 * 创建子类的对象,在堆空间中,就会加载所有父类声明的 ...

  8. Java // 使用二维数组打印 10 行杨辉三角

    1 // 使用二维数组打印 10 行杨辉三角 2 public static void main(String[] args) 3 { 4 //1.声明 并初始化二维数组 5 int[][]yangh ...

  9. 18 Codeforces Round 853 (Div. 2)C. Serval and Toxel's Arrays(算贡献)

    C. Serval and Toxel's Arrays 这种题目做多了应该很容易从贡献的角度去考虑了. 考虑当前版本对答案的贡献,首先这个版本和其他版本取交集至少会包含它本身所以直接先把\(i * ...

  10. 仅需10秒!ChatGPT轻松画出UML用例图,我却苦战10分钟。

    当我们写技术文档时,一张系统用例图,平时要花费10分钟才完成,而ChatGPT绘图过程只用了10秒钟,基本可以达到同样的水平,通过ChatGPT可以显著提高画流程图的效率. 什么是用例图 用例图是统一 ...