使用 xunit 编写测试代码

article/2025/10/20 9:25:10

使用 xunit 编写测试代码

Intro

xunit 是 .NET 里使用非常广泛的一个测试框架,有很多测试项目都是在使用 xunit 作为测试框架,不仅仅有很多开源项目在使用,很多微软的项目也在使用 xunit 来作为测试框架。

Get Started

在 xunit 中不需要标记测试类,所有 public 的类似都可以作为测试类,测试方法需要使用 Fact 或者 Theory 注解来标注方法,来看一个基本的使用示例:

首先准备了几个要测试的方法:

internal class Helper
{public static int Add(int x, int y){return x + y;}public static void ArgumentExceptionTest() => throw new ArgumentException();public static void ArgumentNullExceptionTest() => throw new ArgumentNullException();
}

测试代码:

public class BasicTest
{[Fact]public void AddTest(){Assert.Equal(4, Helper.Add(2, 2));Assert.NotEqual(3, Helper.Add(2, 2));}[Theory][InlineData(1, 2)][InlineData(2, 2)]public void AddTestWithTestData(int num1, int num2){Assert.Equal(num1 + num2, Helper.Add(num1, num2));}
}

使用 Fact 标记的测试方法不能有方法参数,只有标记 Theory 的方法可以有方法参数

使用 Assert 来断言结果是否符合预期,xunit 提供了很丰富的 Assert 方法,可以使得我们的测试代码更加简洁。

Exception Assert

除了一般的结果断言,xunit 也支持 exception 断言,主要支持两大类,Assert.Throw/Assert.Throw<TExceptionType>/Assert.ThrowAny<TExceptionType>,对应的也有 Async 版本

[Fact]
public void ExceptionTest()
{var exceptionType = typeof(ArgumentException);Assert.Throws(exceptionType, Helper.ArgumentExceptionTest);Assert.Throws<ArgumentException>(testCode: Helper.ArgumentExceptionTest);
}[Fact]
public void ExceptionAnyTest()
{Assert.Throws<ArgumentNullException>(Helper.ArgumentNullExceptionTest);Assert.ThrowsAny<ArgumentNullException>(Helper.ArgumentNullExceptionTest);Assert.ThrowsAny<ArgumentException>(Helper.ArgumentNullExceptionTest);
}

Assert.Throw(exceptionType, action)Assert.Throw<TExceptionType>(action) 这样的 exception 类型只能是这个类型,继承于这个类型的不算,会 fail,而 Assert.ThrowAny<TExceptionType>(action) 则更包容一点,是这个类型或者是继承于这个类型的都可以。

Comparisons

很多人已经在使用其他的测试框架,如何迁移呢,xunit 也给出了与 nunit 和 mstest 的对比,详细可以参考下面的对比,具体可以参考 https://xunit.net/docs/comparisons:

NUnit 3.xMSTest 15.xxUnit.net 2.xComments
[Test][TestMethod][Fact]Marks a test method.
[TestFixture][TestClass]n/axUnit.net does not require an attribute for a test class; it looks for all test methods in all public (exported) classes in the assembly.
Assert.That Record.Exception[ExpectedException]Assert.Throws Record.ExceptionxUnit.net has done away with the ExpectedException attribute in favor of Assert.Throws. See Note 1
[SetUp][TestInitialize]ConstructorWe believe that use of [SetUp] is generally bad. However, you can implement a parameterless constructor as a direct replacement. See Note 2
[TearDown][TestCleanup]IDisposable.DisposeWe believe that use of [TearDown] is generally bad. However, you can implement IDisposable.Dispose as a direct replacement. See Note 2
[OneTimeSetUp][ClassInitialize]IClassFixture<T>To get per-class fixture setup, implement IClassFixture<T> on your test class. See Note 3
[OneTimeTearDown][ClassCleanup]IClassFixture<T>To get per-class fixture teardown, implement IClassFixture<T> on your test class. See Note 3
n/an/aICollectionFixture<T>To get per-collection fixture setup and teardown, implement ICollectionFixture<T> on your test collection. See Note 3
[Ignore("reason")][Ignore][Fact(Skip="reason")]Set the Skip parameter on the [Fact] attribute to temporarily skip a test.
[Property][TestProperty][Trait]Set arbitrary metadata on a test
[Theory][DataSource][Theory] [XxxData]Theory (data-driven test). See Note 4

Data Driven Test

测试框架大多提供数据驱动测试的支持,简单的就如开篇中的 Theory 示例,我们再来看一些稍微复杂一些的示例,一起来看下:

要使用数据驱动的方式写测试方法,测试方法应该标记为 Theory,并且将测试数据作为测试方法的方法参数

InlineData

最基本数据驱动的方式当属 InlineData,添加多个 InlineData 即可使用不同的测试数据进行测试

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void InlineDataTest(int num)
{Assert.True(num > 0);
}

InlineData 有其限制,只能使用一些常量,想要更灵活的方式需要使用别的方式,测试结果:

MemberData

MemberData 可以一定程度上解决 InlineData 存在的问题,MemberData 支持字段、属性或方法,且需要满足下面两个条件:

  • 需要是 public

  • 需要是 static

  • 可以隐式转换为 IEnumerable<object[]> 或者方法返回值可以隐式转换为 IEnumerable<object[]>

来看下面的示例:


[Theory]
[MemberData(nameof(TestMemberData))]
public void MemberDataPropertyTest(int num)
{Assert.True(num > 0);
}public static IEnumerable<object[]> TestMemberData =>Enumerable.Range(1, 10).Select(x => new object[] { x }).ToArray();[Theory]
[MemberData(nameof(TestMemberDataField))]
public void MemberDataFieldTest(int num)
{Assert.True(num > 0);
}public static readonly IList<object[]> TestMemberDataField = Enumerable.Range(1, 10).Select(x => new object[] { x }).ToArray();[Theory]
[MemberData(nameof(TestMemberDataMethod), 10)]
public void MemberDataMethodTest(int num)
{Assert.True(num > 0);
}public static IEnumerable<object[]> TestMemberDataMethod(int count)
{return Enumerable.Range(1, count).Select(i => new object[] { i });
}

测试结果:

Custom Data Source

MemberData 相比之下提供了更大的便利和可自定义程度,只能在当前测试类中使用,想要跨测试类还是不行,xunit 还提供了 DataAttribute ,使得我们可以通过自定义方式实现测试方法数据源,甚至也可以从数据库里动态查询出数据,写了一个简单的示例,可以参考下面的示例:

自定义数据源:

public class NullOrEmptyStringDataAttribute : DataAttribute
{public override IEnumerable<object[]> GetData(MethodInfo testMethod){yield return new object[] { null };yield return new object[] { string.Empty };}
}

测试方法:

[Theory]
[NullOrEmptyStringData]
public void CustomDataAttributeTest(string value)
{Assert.True(string.IsNullOrEmpty(value));
}

测试结果:

Output

在测试方法中如果想要输出一些测试信息,直接是用 Console.Write/Console.WriteLine 是没有效果的,在测试方法中输出需要使用 ITestoutputHelper 来输出,来看下面的示例:

public class OutputTest
{private readonly ITestOutputHelper _outputHelper;public OutputTest(ITestOutputHelper outputHelper){_outputHelper = outputHelper;}[Fact]public void ConsoleWriteTest(){Console.WriteLine("Console");}[Fact]public void OutputHelperTest(){_outputHelper.WriteLine("Output");}
}

测试方法中使用 Console.Write/Console.WriteLine 的时候会有一个提示:

测试输出结果:

Console.WriteLine
TestOutputHelper.WriteLine

Test Filter

xunit 提供了 BeforeAfterTestAttribute 来让我们实现一些自定义的逻辑来在测试运行前和运行后执行,和 mvc 里的 action filter 很像,所以这里我把他称为 test filter,来看下面的一个示例,改编自 xunit 的示例:

/// <summary>
/// Apply this attribute to your test method to replace the
/// <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class UseCultureAttribute : BeforeAfterTestAttribute
{private readonly Lazy<CultureInfo> _culture;private readonly Lazy<CultureInfo> _uiCulture;private CultureInfo _originalCulture;private CultureInfo _originalUiCulture;/// <summary>/// Replaces the culture and UI culture of the current thread with/// <paramref name="culture" />/// </summary>/// <param name="culture">The name of the culture.</param>/// <remarks>/// <para>/// This constructor overload uses <paramref name="culture" /> for both/// <see cref="Culture" /> and <see cref="UICulture" />./// </para>/// </remarks>public UseCultureAttribute(string culture): this(culture, culture) { }/// <summary>/// Replaces the culture and UI culture of the current thread with/// <paramref name="culture" /> and <paramref name="uiCulture" />/// </summary>/// <param name="culture">The name of the culture.</param>/// <param name="uiCulture">The name of the UI culture.</param>public UseCultureAttribute(string culture, string uiCulture){_culture = new Lazy<CultureInfo>(() => new CultureInfo(culture, false));_uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture, false));}/// <summary>/// Gets the culture./// </summary>public CultureInfo Culture { get { return _culture.Value; } }/// <summary>/// Gets the UI culture./// </summary>public CultureInfo UICulture { get { return _uiCulture.Value; } }/// <summary>/// Stores the current <see cref="Thread.CurrentPrincipal" />/// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" />/// and replaces them with the new cultures defined in the constructor./// </summary>/// <param name="methodUnderTest">The method under test</param>public override void Before(MethodInfo methodUnderTest){_originalCulture = Thread.CurrentThread.CurrentCulture;_originalUiCulture = Thread.CurrentThread.CurrentUICulture;Thread.CurrentThread.CurrentCulture = Culture;Thread.CurrentThread.CurrentUICulture = UICulture;CultureInfo.CurrentCulture.ClearCachedData();CultureInfo.CurrentUICulture.ClearCachedData();}/// <summary>/// Restores the original <see cref="CultureInfo.CurrentCulture" /> and/// <see cref="CultureInfo.CurrentUICulture" /> to <see cref="Thread.CurrentPrincipal" />/// </summary>/// <param name="methodUnderTest">The method under test</param>public override void After(MethodInfo methodUnderTest){Thread.CurrentThread.CurrentCulture = _originalCulture;Thread.CurrentThread.CurrentUICulture = _originalUiCulture;CultureInfo.CurrentCulture.ClearCachedData();CultureInfo.CurrentUICulture.ClearCachedData();}
}

这里实现了一个设置测试用例运行过程中 Thread.CurrentThread.Culture 的属性,测试结束后恢复原始的属性值,可以用作于 Class 也可以用在测试方法中,使用示例如下:

[UseCulture("en-US", "zh-CN")]
public class FilterTest
{[Fact][UseCulture("en-US")]public void CultureTest(){Assert.Equal("en-US", Thread.CurrentThread.CurrentCulture.Name);}[Fact][UseCulture("zh-CN")]public void CultureTest2(){Assert.Equal("zh-CN", Thread.CurrentThread.CurrentCulture.Name);}[Fact]public void CultureTest3(){Assert.Equal("en-US", Thread.CurrentThread.CurrentCulture.Name);Assert.Equal("zh-CN", Thread.CurrentThread.CurrentUICulture.Name);}
}

测试结果如下:

Shared Context

单元测试类通常共享初始化和清理代码(通常称为“测试上下文”)。xunit 提供了几种共享初始化和清理代码的方法,具体取决于要共享的对象的范围。

  • 构造器和 Dispose 方法 (共享初始化和 Dispose,不需要共享对象)

  • Class Fixtures (同一个测试类中共享对象)

  • Collection Fixtures (同一个 Collection 中(可以是多个测试类)中共享对象实例)

通常我们可以使用 Fixture 来实现依赖注入,但是我更推荐使用 Xunit.DependencyInjection 这个项目来实现依赖注入,具体使用可以参考之前的文章 在 xunit 测试项目中使用依赖注入 中的介绍

More

希望对你使用 xunit 有所帮助

文章中的示例代码可以从 https://github.com/WeihanLi/SamplesInPractice/tree/master/XunitSample 获取

xunit 还有很多可以扩展的地方,更多可以参考 xunit 的示例 https://github.com/xunit/samples.xunit

References

  • https://github.com/WeihanLi/SamplesInPractice/tree/master/XunitSample

  • https://github.com/xunit/samples.xunit

  • https://xunit.net/#documentation

  • https://xunit.net/docs/comparisons

  • https://xunit.net/docs/shared-context

  • 在 xunit 测试项目中使用依赖注入


http://chatgpt.dhexx.cn/article/E2ukOBd3.shtml

相关文章

C#_Unit Testing 一(xUnit)

一、前言 在VS中新建一个xunit的项目&#xff0c;该项目中已经自动安装了一些Nuget包&#xff0c;其中一个关键的就是xunit&#xff0c;https://xunit.net/。 同时&#xff0c;在同一个解决方案下我们也新建了一个类库&#xff0c;这个类库就是需要被测试的。这里提一点&…

在.NET开发中的单元测试工具之(2)——xUnit.Net

在上一篇《在.NET开发中的单元测试工具之(1)——NUnit》中讲述了如何使用NUnit在.NET开发中进行单元测试以及NUnit的一些缺点&#xff0c;今天将讲述如何使用xUnit.Net来进行单元测试。 xUnit.Net介绍 xUnit.net的创造者的创造者是Jim Newkirk和Brad Wilson从包括NUnit及其它单…

单元测试中Assert详解-xUnit

前一篇&#xff1a;详谈单元测试-xUnit 简介 Assert 是基于代码的返回值、对象的最终状态、事件是否发生等情况来评估测试的结果。Assert 的结果可能是 Pass 或者 Fail。如果所有的 Asserts 都通过了&#xff0c;那么整个测试就通过了。如果任何 Asserts 失败了&#xff0c;那…

xUnit-Moq框架

基于上一次的单元测试-xUnit进行 Models文件夹 Staff类修改为&#xff1a; public class Staff { public int Id { get; set; } public string Name { get; set; } public string State { get; set; } public int Age { get; set; } } 创建一个IRepository接口…

xUnit.net入门

xUnit.net是一个免费的、开源的、以社区为中心的.net框架单元测试工具。 本文在Win10Visual Studio2022-Preview下&#xff0c;创建一个简单的.Net Framework4.8的xUnit.net测试项目。 1、新建项目 打开VS2022&#xff0c;新建项目&#xff0c;弹出“创建新项目”窗口&#…

xUnit总结--学习笔记

xUnit.net是针对.NET Framework的免费&#xff0c;开源&#xff0c;以社区为中心的单元测试工具。 自动化测试的优点# 可以频繁的进行测试可以在任何时间进行测试&#xff0c;也可以按计划定时进行&#xff0c;例如&#xff1a;可以在半夜进行自动化测试比人工测试速度快可以更…

Xunit入门

本节记录Xunit单元测试的入门知识&#xff0c;以2.1版本作为入门示例。 1、新建一个类库项目 2、在Nuget中搜索xunit&#xff0c;这里我们只选xUnit.net和xunit.runner.visualstudio包。 其中xUnit是框架&#xff0c;而xunit.runner.visualstudio是vs插件包&#xff0c;让我们可…

3. 使用xUnit进行单元测试

实现.NET Core时&#xff0c;xUnit可用于创建单元测试&#xff0c;.NET Core团队使用了该产品。xUnit是一个开源实现方案&#xff0c;创建NUnit 2.0的开发人员创建了它。现在&#xff0c;.NET Core命令行界面支持MSTest和xUnit。 提示&#xff1a; xUnit的文档可参阅https://…

详谈单元测试-xUnit

简介 xUnit.net 是针对 .NET 的免费&#xff0c;开源单元测试框架&#xff0c;可并行测试、数据驱动测试。测试项目需引用被测试项目&#xff0c;从而对其进行测试&#xff0c;测试项目同时需要引用 xUnit。测试编写完成后&#xff0c;用 Test Runner 来测试项目&#xff0c;T…

01 如何利用xUnit框架对测试用例进行维护-xUnit简介及基本使用方法(基于Junit4)

1、xUnit是什么 先看Wikipedia上的解释 xUnit是一系列测试框架的统称&#xff0c;最开始来源于一个叫做Smalltalk的SUnit框架&#xff0c;现在各种面向对象的语言&#xff0c;如Java、Python的鼻祖就是Smalltalk&#xff0c;后来这些语言都借助了Sunit框架的理念&#xff0c;有…

【IoT】物联网NB-IoT之移动oneNET平台硬件接入

主要实现开发者实际的终端设备在 OneNET 平台上的创建、连接和数据交互。在完成用户注册和产品创建后&#xff0c;即可根据相关所创建产品的协议类型选择相应的硬件接入的开发。 接入流程可参见下图&#xff1a; 1、LWM2M 协议 - NB-IoT 测试接入流程分为平台域和设备域&…

【安装工具】【ARM-DS-5】成功破解ARM DS-5 v5.26.0 + 配置高通MDM9026芯片的demo==》成功编译得到demo.bin文件

Note&#xff1a;Win7 64环境 安装破解ARM DS-5 v5.26.0 http://blog.csdn.net/qq_27295631/article/details/68582582 验证破解成功 安装MinGW http://blog.csdn.net/qq_27295631/article/details/68582582 设置系统属性-环境变量(bin/lib/inc) ARMBIN C:\Program Files…

把乌托邦变成细密画:华为如何思考家居IoT?

奥尔罕帕穆克凭借他的名作《我的名字叫红》一举跃入20世纪文学史&#xff0c;而这部广为人知的作品也让我们知道了一种绘画史上非常奇特的艺术形式&#xff1a;细密画。 这种流传自希腊罗马&#xff0c;盛行于波斯帝国的艺术样式&#xff0c;特点是要在书的扉页、边框&#xff…

MDM9607平台 Secure Boot调试记录

本博客只涉及技术问题,尊重原创,不涉及商用问题。 目录 一 概述... 4 二 原理及流程... 4 2.1 安全启动原理... 4 2.2 安全启动结构图:... 5 2.3 安全启动流程图... 5 三 证书简介... 6 四 签名镜像格式... 7 五 熔丝熔断... 7 5.1 熔断使能... 7 5.2 熔断过程..…

MDM9205简介

大家好&#xff0c;今天小白给大家介绍下高通新推出的下一代物联网专用调制解调器Qualcomm 9205&#xff0c;欢迎一起学习交流。 去年12月17日&#xff0c;高通宣布推出下一代物联网&#xff08;IoT&#xff09;专用调制解调器Qualcomm 9205。全新Qualcomm 9205 LTE调制解调器…

基于机智云gokit4.0(G)和MDM9206的 小型智能气象站

项目介绍 小型气象站广泛应用于气象、农业、环境检测和治理等领域。小型气象站对空气温湿度、光照、风速、风向、雨量、土壤湿度、蒸发量、大气压力等环境气象要素进行全天候现场检测。通过更换不同的传感器&#xff0c;该装置也可用于楼宇环境监控、安防、智慧农业等领域。 由…

MWC2018 机智云发布gokit4.0G全栈IoT开发套件,支持高通MDM9206

MWC2018 机智云发布gokit4.0G全栈IoT开发套件&#xff0c;支持高通MDM9206 NB-IoT/eMTC 继2月14日Qualcomm高通宣布&#xff0c;推出面向Qualcomm MDM9206LTE IoT全球多模调制解调器的全新LTEIoT软件开发包&#xff08;SDK&#xff09;&#xff0c;并已预集成机智云物联网云平…

【沙龙】基于MDM9206芯片的gokit4(G)的应用实操

导读 紧跟前沿通信技术、Get最新开发技能&#xff0c;高通Qualcomm&机智云&移远通信高级工程师手把手教开发&#xff1a;通过GoKit4(G)MDM9206快速接入机智云&#xff0c;4小时掌握高通MDM9206 C-IoT SDK实现不同IoT应用场景的方法&#xff0c;实现NB-IoT产品原型设计…

在线公开课】基于MDM9206的GoKit4(G)在线公开课

【在线公开课】基于MDM9206的GoKit4(G)在线公开课 基于Qualcomm C-IoT SDK的物联网开发以及GoKit4应用开发指导 MDM9206 LTE IoT调制解调器是一款专为支持全球多模功能而打造的解决方案&#xff0c;它可支持eMTC&#xff08;Cat M1&#xff09;、NB-IoT&#xff08;Cat NB-1&…