3. 使用xUnit进行单元测试

article/2025/10/20 12:26:35

实现.NET Core时,xUnit可用于创建单元测试,.NET Core团队使用了该产品。xUnit是一个开源实现方案,创建NUnit 2.0的开发人员创建了它。现在,.NET Core命令行界面支持MSTest和xUnit。

提示:

xUnit的文档可参阅https://xunit.github.io/

Visual Studio测试环境支持其他测试框架。测试适配器,如NUnit、xUnit、Boost(用于C++)、Chutzpah(用于JavaScript)和Jasmine(用于JavaScript)可通过扩展和更新来使用;这些测试适配器与Visual Studio Test Explorer集成。

xUnit是.NET Core中一个杰出的测试框架,也由微软的.NET Core和ASP .NET Core开源代码使用,所以xUnit是本节的重点。

1. 使用xUnit和.NET Core

使用.NET Core应用程序,可以创建xUnit测试,其方式与MSTest测试类似。从命令行,可以使用:

> dotnet new xunit

创建xUnit测试项目。在Visual Studio 2017中,可以选择项目类型xUnit Test Project(.NET Core)。

在示例项目中,测试与以前相同的.NET 标准库UnitTestingSamples。这个库包含之前所示的测试的类型:DeepThought和StringSample。测试项目的名称是UnitTestingSamples.xUnit.Tests。

这个项目需要引用xunit(对于单元测试,是xunit.runner.visualstudio[在Visual Studio中运行测试])和UnitTestingSamples项目(应测试的代码)。为了与.NET Core命令行集成,添加dotnet-xunit的DotNetCliToolReference

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netcoreapp3.1</TargetFramework><IsPackable>false</IsPackable></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /><PackageReference Include="xunit" Version="2.4.0" /><PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /><PackageReference Include="coverlet.collector" Version="1.2.0" /></ItemGroup><ItemGroup><ProjectReference Include="..\UniTestingSamples\UniTestingSamples.csproj" /></ItemGroup></Project>

2. 创建Fact属性

创建测试的方式非常类似于之前的方法。在MSTest中,需要给测试类型添加特性注释([TestClass])。但在xUnit中是不必要的。因为会在所有的公共类中搜索测试方法。在xUnit和MSTest中测试方法TheAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything的差异只是测试方法带Fact特性注释和不同的Assert.Equal方法:

    public class DeepThoughtTest{[Fact]public void ResultOfTheAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything(){int expected = 42;var dt = new DeepThought();int actual = dt.TheAnswerOfTheUltimateQuestionOfLifeUniverseAndEverything();Assert.Equal(expected,actual);}}

现在使用的Assert类在xUnit名称空间中定义。与MSTest的Assert方法相比,这个类定义了更多的方法,用于验证。例如,不是添加一个特性来指定预期的异常,而是使用Assert.Throws方法,允许在一个测试方法中多次检查异常:

    public class StringSampleTest{[Fact]public void GetStringDemoExceptions(){var sample = new StringSample(string.Empty);Assert.Throws<ArgumentNullException>(()=>sample.GetStringDemo(null,"a"));Assert.Throws<ArgumentNullException>(() => sample.GetStringDemo("a",null));Assert.Throws<ArgumentException>(()=>sample.GetStringDemo(string.Empty,"a"));}}

3. 创建Theory特性

xUnit为不需要参数的测试方法定义Fact特性。使用xUnit还可以调用需要参数的单元测试方法:使用Theory特性提供数据,添加一个派生于Data的特性。这样就可以通过一个方法定义多个单元测试了。

在下面的代码片段中,Theory特性应用于GetStringDemoInlineData单元测试方法。StringSample.GetStringDemo方法定义了取决于输入数据的不同路径。如果第二个参数传递的字符串不包含在第一个参数中,就到达第一条路径。如若第二个字符串包含在第一个字符串的前5个字符串中,就到达第二条路径。第三条路径是用else子句到达的。要到达所有不同的路径,3个InlineData特性应用于测试方法。每个特性都定义了4个参数,它们以相同的顺序直接发送到单元测试方法的调用中。特性还定义了被测试方法应该返回的值:

        [Theory][InlineData("","longer string","nger","removed nger from longer string: lo string")][InlineData("init","longer string","string","INIT")]public void GetStringDemoInlineData(string init,string a,string b,string expected){var sample = new StringSample(init);string actual = sample.GetStringDemo(a,b);Assert.Equal(expected,actual);}

特性InlineData派生于Data特性。除了通过特性直接把值提供给测试方法之外,值也可以来自于属性、方法或类。以下例子定义了一个静态方法,它用IEnumeralbe<object[]>对象返回相同的值:

        public static IEnumerable<object[]> GetStringSampleData()=> new []{new object[]{ "","a","b","b not found in a"},new object[]{"","longer string","nger","removed nger from longer string: lo string"},new object[]{ "init","longer string","string","INIT"}};

单元测试方法现在用MemberData特性改变了。这个特性允许使用返回IEnumerable<object>的静态属性或方法,填写单元测试的参数:

        [Theory][MemberData(nameof(GetStringSampleData))]public void GetStringDemoMemberData(string init, string a, string b, string expected){var sample = new StringSample(init);var actual = sample.GetStringDemo(a, b);Assert.Equal(expected, actual);}

测试运行结果:

4. 使用Mocking库

下面是一个更复杂的例子:在MVVM应用程序中,为客户端服务库创建一个单元测试。本章的示例代码仅包含该应用程序使用的一个库。这个服务使用依赖注入功能,注入接口IBooksRepository定义的存储库。用于测试AddOrUpdateBookAsync方法的单元测试不应该测试该库,而只测试方法中的功能。对于库,应执行另一个单元测试:下面的代码段显示了类的实现:

    public class BooksService : IBooksService{private ObservableCollection<Book> _books = new ObservableCollection<Book>();public IEnumerable<Book> Books => _books;private IBookRepository _booksRepository;public BooksService(IBookRepository booksRepository){_booksRepository = booksRepository;}public async Task<Book> AddOrUpdateBookAsync(Book book){if (book is null){throw new ArgumentNullException(nameof(book));}Book updated = null;if (book.BookId == 0){updated = await _booksRepository.AddAsync(book);_books.Add(updated);}else{updated = await _booksRepository.UpdateAsync(book);if (updated is null){throw new InvalidOperationException();}Book old = _books.Where(b => b.BookId == updated.BookId).Single();int ix = _books.IndexOf(old);_books.RemoveAt(ix);_books.Insert(ix,updated);}return updated;}public Book GetBook(int bookId){return _books.Where(b => b.BookId == bookId).SingleOrDefault();}public async Task LoadBooksAsync(){if (_books.Count > 0){return;}IEnumerable<Book> books = await _booksRepository.GetItemAsync();//_books.Clear();foreach (var book in books){_books.Add(book);}}}

因为AddOrUpdateBookAsync的单元测试不应该测试用于IBooksRepository的存储库,所以需要实现一个用于测试的存储库。为了简单起见,可以使用一个模拟库自动填充空白。一个常用的模拟库是Moq。对于单元测试项目,添加NuGet包Moq。

注意:

除了使用Moq框架之外,还可以用示例数据实现一个内存中的存储库。在用户界面的设计过程中,可以这么做来处理应用程序的示例数据

使用xUnit时,每次运行测试都会创建测试类的一个新实例。如果多个测试需要相同的功能,就可以把这个功能移动到构造函数中。如果每次运行测试后需要释放资源,就可以实现IDisposable接口。

在BooksServiceTest类的构造函数中,实例化一个Mock对象,传递泛型参数IBooksRepository。Mock构造函数创建接口的实现代码。因为需要从存储库中得到一些非空结果来创建有用的测试,所以Setup方法定义可以传递的参数,ReturnAsync方法定义了方法存根返回的结果。使用Mock类的Object属性访问模拟对象,并传递它,以创建BooksService类的实例。有了这些设置,就可以实现单元测试:

    public class BooksServiceTest : IDisposable{public void Dispose(){}private const string TestTitle = "Test Title";private const string UpdatedTestTitle = "Updated Test Title";private const string APublisher = "A Publisher";private BooksService _bookService;private Book _newBook = new Book{BookId = 0,Title = TestTitle,Publisher = APublisher};private Book _expectedBook = new Book{BookId = 1,Title = TestTitle,Publisher = APublisher};private Book _notInRepositoryBook = new Book{BookId = 42,Title = TestTitle,Publisher = APublisher};private Book _updatedBook = new Book{BookId = 1,Title = UpdatedTestTitle,Publisher = APublisher};public BooksServiceTest(){var mock = new Mock<IBookRepository>();mock.Setup(repository =>repository.AddAsync(_newBook)).ReturnsAsync(_expectedBook);mock.Setup(repository =>repository.UpdateAsync(_notInRepositoryBook)).ReturnsAsync(null as Book);mock.Setup(repository=>repository.UpdateAsync(_updatedBook)).ReturnsAsync(_updatedBook);_bookService = new BooksService(mock.Object);}[Fact]public void Test1(){}}

实现的第一个单元测试AddOrUpdateBookAsync_ThrowFouNull证明,如果把null传递给AddOrUpdateBookAsync方法,就会抛出ArgumentNullException异常。该实现代码只需要在构造函数中实例化成员变量_booksService,而不需要模拟设置。这个代码示例还说明,单元测试方法可以实现为返回Task的异步方法:

        public async Task AddOrUpdateBookAsync_ThrowNullException(){//arrangeBook nullBook = null;//act an assertawait Assert.ThrowsAsync<ArgumentNullException>(() =>_bookService.AddOrUpdateBookAsync(nullBook));}

单元测试方法AddOrUpdateBookAsync_AddedBookReturnsFromRepository给服务添加了一本新书(变量_newBook),并期望返回_expectedBook对象。在AddOrUpdateBookAsync方法的实现代码中,调用了IBooksRepository的AddAsync方法,因此,可以应用以前给这个方法定义的模拟设置。这个方法的结果应是,返回的Book等于_expectedBook,_expectedBook也需要添加到BooksService的图书集合中:

        public async Task AddOrUpdateBookAsync_AddedBookReturnsFromRepository(){//arrange in constructory//actBook actualAdded = await _bookService.AddOrUpdateBookAsync(_newBook);//assertAssert.Equal(_expectedBook, actualAdded);Assert.Contains(_expectedBook,_bookService.Books);}

AddOrUpdateBookAsync_UpdateNotExistingBookThrows单元测试证明,尝试更新服务中不存在的图书,应抛出InvalidOperationException异常:

        public async Task AddOrUpdateBookAsync_UpdateNotExistingBookThrow(){//arrange in constructor//act and assertawait Assert.ThrowsAsync<InvalidOperationException>(()=>_bookService.AddOrUpdateBookAsync(_notInRepositoryBook));}

更新图书的常见情形用单元测试AddOrUpdateBookAsync_UpdateBook来处理。这里需要做额外的准备,在更新前,先把图书添加到服务中:

        [Fact]public async Task AddOrUpdateBookAsync_UpdateBook(){//arrangeawait _bookService.AddOrUpdateBookAsync(_newBook);//actBook updateBook = await _bookService.AddOrUpdateBookAsync(_updatedBook);//assertAssert.Equal(_updatedBook, updateBook);Assert.Contains(_updatedBook,_bookService.Books);}

当使用MVVM模式与基于XAML的应用程序,以及使用MVC模式和基于Web的应用程序时,会降低用户界面的复杂性,减少复杂UI测试的需求。然而,仍有一些场景应该用于UI测试,例如,浏览页面、拖拽元素等。此时应使用Visual Studio的UI测试功能。


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

相关文章

详谈单元测试-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&…

MDM9206简介

MDM9206简介 大家好&#xff0c;今天小白给大家介绍一下当前很火热的一款多模多频的高通的NB-IOT芯片&#xff0c;欢迎一起讨论学习。 1. MDM9206芯片的发展背景 接下来我们看看该款具有代表性的多模多频NB-IOT芯片是基于什么样的大背景下产生的&#xff1f; 从2G、3G如今的…

VMware虚拟机centOS7的终端命令:权限不够

我的VMware虚拟机centOS7在终端命令的时候老是跟我说权限不够&#xff1a; 然后我就去网上查查查&#xff01;有一个大佬的博客非常完美的解决了我的问题&#xff01; (5条消息) VM虚拟机CentOS7提升用户权限_JzjSunshine的博客-CSDN博客 现在&#xff1a; 但是我高兴了太早我执…

centos中常用命令

centos中常用命令 一:使用 CentOS 常用命令查看 cpu 如果觉得需要看的更加舒服 grep “model name” /proc/cpuinfo | cut -f2 -d: 二.使用 CentOS 常用命令查看 cpu 是 32 位还是 64 位 查看 CPU 位数(32 or 64) 三:使用 CentOS 常用命令查看当前 linux 的版本 more /etc/r…

CentOS 7关闭防火墙命令

1、命令行界面输入命令“systemctl status firewalld.service”并按下回车键。 2、然后在下方可以查看得到“active&#xff08;running&#xff09;”&#xff0c;此时说明防火墙已经被打开了。 3、在命令行中输入systemctl stop firewalld.service命令&#xff0c;进行关闭…

centos命令源码获取

源码链接 https://vault.centos.org/7.9.2009/os/Source/SPackages/ 查看本地命令版本 which ls rpm -qf /usr/bin/ls下载指定命令的rpm包 wget https://vault.centos.org/7.9.2009/os/Source/SPackages/coreutils-8.22-24.el7.src.rpm提取源码 rpm2cpio coreutils-8.22-24.…

centos下所有命令都不能用

问题描述&#xff1a; 解决办法&#xff1a; --- export PATH"/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin:/bin:/sbin" 总的来说&#xff0c;不说运行在linux上的一些应用&#xff0c;或者你之前部署过的产品&#xff0c;就是很多linux的基本命令&#xff0…

centos linux升级命令,在CentOS系统下包更新的命令

在CentOS系统下包更新的命令 更新和升级yum update 全部更新 yum update package1 更新指定程序包package1 yum check-update 检查可更新的程序 yum upgrade package1 升级指定程序包package1 yum groupupdate group1 升级程序组group1 更多命令 一、安装yum install 全部安装 …

centos常用命令大全

文章目录 命令帮助查询操作压缩、解压网络相关文件操作命令文件与目录操作查看文件内容文本内容处理yum安装器系统相关简单命令修改密码策略 命令帮助 【要执行的命令】 --help 查询操作 全局查找 find / -name 【查询的内容】 压缩、解压 解压tar.gz结尾的压缩文件 tar -…