首页 > 其他分享 >.Net单元测试xUnit和集成测试指南(1)

.Net单元测试xUnit和集成测试指南(1)

时间:2024-04-12 10:23:47浏览次数:18  
标签:int 单元测试 测试数据 测试 xUnit Net 属性

合集 - 单元测试(3)   1..Net单元测试xUnit和集成测试指南(1)04-03 2.单元测试篇2-TDD三大法则解密04-07 3.掌握 xUnit 单元测试中的 Mock 与 Stub 实战04-12 收起  

引言

在现代化的软件开发中,单元测试集成测试是确保代码质量和可靠性的关键部分。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 创建单元测试项目

image

在项目创建完之后我们可以简单浏览一下 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);
    }
}

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

image

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

单元测试命名规范

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

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

示例

    [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 适用于动态、灵活的测试数据集合,适合于需要从外部源动态获取测试数据的情况。

image

自定义属性

除了上面提到的 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,您可以在测试运行时将调试信息、日志信息等输出到测试结果中,方便调试和查看测试过程中的输出信息。

调试

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

最后

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

本文完整源代码

 

2024-04-12 10:07:07【出处】:https://www.cnblogs.com/ruipeng/p/18112221

=======================================================================================

标签:int,单元测试,测试数据,测试,xUnit,Net,属性
From: https://www.cnblogs.com/mq0036/p/18130618

相关文章

  • Kubernetes Pod配置:从基础到高级实战技巧
    本文深入探讨了KubernetesPod配置的实战技巧和常见易错点。关注【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营......
  • 掌握 xUnit 单元测试中的 Mock 与 Stub 实战
    引言上一章节介绍了TDD的三大法则,今天我们讲一下在单元测试中模拟对象的使用。FakeFake-Fake是一个通用术语,可用于描述stub或mock对象。它是stub还是mock取决于使用它的上下文。也就是说,Fake可以是stub或mockMock-Mock对象是系统中的fake对象,用于确......
  • 卷积神经网络——残差网络(ResNet)
    原文链接:https://blog.csdn.net/weixin_52963585/article/details/124903050在实际的试验中发现,随着卷积层和池化层的叠加,不但没有出现学习效果越来越好的情况,反而两种问题:梯度消失和梯度爆炸梯度消失:若每一层的误差梯度小于1,反向传播时,网络越深,梯度越趋近于0梯度爆炸:若每一层的......
  • 轻量化网络——MobileNet
    原文链接:https://zhuanlan.zhihu.com/p/402766063作为轻量化网络的经典网络,MobileNet自诞生就被广泛应用于工业界。笔者也经常在结构设计中使用MobileNet的诸多设计思想。本文参考众多大神文章,较详细介绍MobileNet系列的设计及改进思想,力求温故知新,举一反三。MobileNetV1Mobil......
  • 论文解读(UGfromer)《Universal Graph Transformer Self-Attention Networks》
    Note:[wechat:Y466551|可加勿骚扰,付费咨询]论文信息论文标题:UniversalGraphTransformerSelf-AttentionNetworks论文作者:论文来源:2022aRxiv论文地址:download论文代码:download视屏讲解:click1-摘要我们引入了一个基于变压器的GNN模型,称为UGfromer,来学习图表示。特别......
  • Asp .Net Core 系列:集成 Refit 和 RestEase 声明式 HTTP 客户端库
    背景.NET中有没有类似Java中Feign这样的框架?经过查找和实验,发现在.NET平台上,虽然没有直接的Feign框架的端口,但是有一些类似的框架和库,它们提供了类似的功能和设计理念。下面是一些在.NET中用于声明式HTTP客户端的框架和库:Refit:Refit是一个用于构建声明式、类......
  • 01_在NET中使用RabbitMQ
    1.Linux上安装Docken服务器系统版本以及内核版本:cat/etc/redhat-release查看服务器内核版本:uname-r安装依赖包:yuminstall-yyum-utilsdevice-mapper-persistent-datalvm2设置阿里云镜像源:yum-config-manager--add-repohttps://mirrors.aliyun.com/docker-ce/linu......
  • Redis Stack功能介绍及redis-om-dotnet使用示例
    为了简化开发人员对较新的 Redis模块及其提供的功能的体验,同时简化支持其功能的文档和客户端。以帮助开发人员从开始使用 Redis的那一刻起,就能充分提高工作效率。RedisStack诞生了。RedisStack将较新的Redis模块整合到一个产品中。这使得我们可以轻松地开始使用我们基于......
  • docker network能干啥
    Docker网络是什么 重要:容器IP变动的时候可以通过服务名直接网络通信而不受到影响。 网络模式:有4个模式,默认是bridge模式  ps:docker容器中的IP是可能会发生变化的。container:新创建的容器不会创建自己的网卡和配置自己的IP.而是和一个指定的容器共享IP,端口范围......
  • ADO.NET连接池写法
     usingSystem;usingSystem.Data;usingSystem.Data.SqlClient;classProgram{staticvoidMain(){//连接字符串,其中包含连接池的配置stringconnectionString="Server=mySqlServerAddress;Database=myDataBase;IntegratedSecurity=Tru......