系列目录
为了支持跨平台,微软为.net平台提供了.net core test sdk,这样第三方测试框架诸如Nunit,Xunit等只需要按照sdk提供的api规范进行开发便可以被dotnet cli工具调用,这样就解决了在持续集成过程中第三方框架依赖于windows平台上的各自runner的问题,使得测试框架开发者不需要花费很大功夫就可以快速迁移到.net core平台,同时封装了各测试框架的实现细节,对外暴露统一调用接口,大大减少devops开发者的工作量.
作为单元测试基础知识介绍,这里只介绍常用单元测试框架Nunit和Xunit如何在.net core平台上使用,并介绍由于.net core的变化所引起的需要注意的测试代码的相应改变,对于如何在Jenkins环境中自动完成测试的相关内容Jenkins基础知识里面介绍
考虑到实际工作中可能有的项目组已经使用或者尝试使用xunit,并且.net core对xunit支持较好,诸多开源项目的单元测试也都使用的是Xunit,本章节作为补充对Xunit基础知识进行讲解,以帮助还不太了解这个框架的同学快速入门.
我们知道在.net framework下相要对mvc项目进行集成测试非常困难,一方面.net http管道里有很多黑盒子,开发者对它的实现细节一无所知,二者需要mock的对象太多,工作量巨大.因此很难在持续集成环境中对web项目进行集成测试.开发者或者测试人员大都依赖postman,fiddler,以及浏览器的http请求插件来进行集成测试,这样带来的一个很大问题这些http请求很难复用,更难以有效的组织管理.即使使用postman这样强大的http工具如果测试接口过多代码也会变得一塌糊涂,过一段时间后想要知道哪个方法是测试哪个接口用的就需要通过搜索来导航到指定的测试方法,并且很多时候有于各模块有相同名称的方法,往往需要先找到方法所在的area,然后再找到controller然后再找到相应方法...如果出现问题的代码过多往往把开发者搞的焦头烂额,苦不堪言.
幸运的是在.net core里很容易模拟一个httpt管道,这一方面使得集成测试在持续集成环境中使用提供了可能,另一方http请求写成程序里,可以很方便的导航到指定的测试方法,极大提供可维护性.本章节最后会介绍如何搭建一个.net core web项目的selfhost环境以供在单元测试框架中使用.
下面我们将简要介绍如何在vs中配置xunt环境以及Xunit断言的基本使用
.net core 中使用Xunit
Xunit是.net平台下的一款单元测试工具,类似Nunit.但是更为轻量,更加专注于单元测试而不像Nunit提供了很多额外的功能
.net core对Xunit支持较好,VisualStudio 2017提供有一个Xunit单元测试模板可以很方便的创建一个Xunit单元测试项目.
如图,在visual studio里创建项目时,选择.net core项目,然后从模板里面找到Xunit单元测试项目便可以创建一个Xunit单元测试项目了.
我们打开刚创建的项目右键选择"Nuget包管理",从包管理工具里我们可以看到,实际上这个模板引用了xunit,和xunit.runner.visualstudio这两个包.这样,我们也可以自己手动创建一个.net core类型的库文件,然后引入这两个包,能达到同样的效果.
这里建议大家通过模板来创建单元测试项目,因为单元测试框架不同版本可能需要引用不同的包,没有经验的同学常常由于引用的包不对导致单元测试项目跑不起来,搞得灰头土脸,非常郁闷.
我们编写以下单元测试代码
public class UnitTest1
{
[Fact]
public void Test1()
{
int intt = 3 + 2;
Assert.True(intt==5);
}
}
通过以上代码我们看到Xunit就测试断言和Nunit很类似(这里指Nunit3,早期版本Nunit差异较大,建议大这在工作中也尽量选用Nunit3,而不是1或者2)
这里有一点差异需要指出,Xunit并不需要对单元测试类进行注解(Nunit是需要的,否则无法识别),只需要在需要测试的方法上加上fact注解即可.
单元测试方法的运行也和前面讲的Nunit单元测试运行方法相同,这里不再赘述.
常见基本断言
虽然Xunit和Nunit在断言上有很多相似的地方,并且有越来越像的趋势,但是仍然有不少差别,因此这里仍然会对Xunit的断言功能进行一个全面的列举,以供大家速查.并且有时候会指出它和Nunit的差别或者指出Nunit中比较难以实现或者技巧性很强的功能如何在Xunit里实现.如果有读者直接阅读本章节而没有了解过Nunit,可以有选择的略过二者比较的内容.
这里首先指出一个很大的差别.Xunit里并没有像Nunit里的stringAssertion,FileAssertion和CollectionAssertion,而是所有的断言都在Assert静态类里,方法也不是很多,语义也相对更加明确,很适应没有单元测试基础的同学快速入门.
下面开始介绍Xunit里的断言方法
Assert.Null
用于断言一个对象是否是Null
这个方法有一个相对含义的断言就是Assert.NotNull,很多其它的方法也有带Not的断言,很容易理解.
Assert. Assert.Equal
此方法有很多重载,用于比较两个字符串,int,decimal或者对象类型是否相等.
注意这里比较两个对象是否相等时,相当于Object.equals()来比较两个对象是否相等
这个方法用于比较两个double类型值是否相等时,可以指定精度.
Assert.StrictEqual
从字面上来看,它用于比较两个对象是否是严格相等,然而它的表现行为和以上Equal方法非常类似,并不是比较两个对象内存地址是否相等
.实际上它是在比较的时候指定一个默认的比较器
.这个方法着实非常让人困惑.
Assert.same
用于比较两个对象运行是是否指定同一块内存地址,如果要比较两个对象是否完全相等,则使用它.
注意,虽然Assert.same接收的是两个object类型对象,但是不建议用它来比较简单类型,它其实是用于比较两个对象是否指向同一内存地址,因此只有比较引用对象才是有意义的.这个方法应该设计成泛型方法才比较好,不知道为什么要这样设计.
Assert.StartsWith Assert.EndsWith
用于断言一个字符串是否以特定字符(串)开头(结尾),并且这两个方法都有一个重载用于指定是否忽略大小写
用过Nunit的同学可能知道,Nunit里要实现区分大小写的StartsWith有点麻烦.
Assert.True Assert.False
用于断言两个布尔变量(包括可空)的值是否是真(假).
Assert.All
用于断言集合里的所有元素是否都满足通过测试,奇怪的是这个方法接收的是Action 委托而不是Func
下面举个用于断言集合里的元素值是否都大于0的例子来看看如何使用它
[Fact]
public void Test1()
{
int[] intt = {3, 4, 5, 9, 22};
Assert.All(intt, t => Assert.True(t > 0));
}
注意以上写法,由于不接收FuncAssert.All(intt, t => t>0);
这样将导致编译错误.
Assert.Contains
这个方法有多个重载,功能也非常多,但是语义都非常明确.
用于断言字符串是否包含指定字符串
相当于字符串里的Contains,并且表现行为类似,也有一个重载支持不区分大小写
用于断言集合是否包含指定元素
请看以下代码
[Fact]
public void Test1()
{
int[] intt = {3, 4, 5, 9};
Assert.Contains(4, intt);
}
以上代码用于断言集合intt里是否包含元素4,显然是包含的
注意,对于包含引用对象的集合判断是否包含某一元素这一个元素必须和集合中的某一个元素的引用地址一样.这很多时候并不是我们想要的行为,多数时候引用对象的对应的字段分别相等时我们就认为它相等,更极端的情况是某些情况下两个对象只有某一个或者少数几个字段相等时我们也认为相等,这要看具体实际业务.前面讲Nunit时对这个问题有过详细讲解.这里Contains方法同样有一个重载以支持一个
比较器
,用于自定义相等性逻辑.
还有一点需要指出,Contains方法只能断言集合是否包含某
一个
元素,而不能断言是否包含某几个元素(也即一个集合是否是另一个集合的子集),Nunit里并没有提供直接方法用于处理这样的问题.有些同学可能认为Assert.Subset是用来解决这个问题的,然而并不是,Subset只能用于实现了ISet接口的集合,很多时候并不是特别有帮助.
用于断言集合中是否包含指定类型的元素
这个重载方法语义稍显不是很明确,它其实相当于linq里的any方法,只要有一个(一些)满足条件的元素就会返回true
[Fact]
public void Test1()
{
int[] intt = {3, 4, 5, 9};
Assert.Contains(intt, a => a > 3);
}
以上方法用于断言intt集合中是否包含大于3的元素,显然是包含的.
Assert.Empty
用于断言集合是否不包含任何元素,也即集合是否是一个空集合
Assert.Matches
此方法接收两个参数,第一个表示要匹配的正则规则,第二个表示要测试的字符串,语义类似正则表达式里的IsMatch
Assert.InRange
用于断言指定元素是否在指定的范围内
[Fact]
public void Test1()
{
Assert.InRange(20, 3, 20);
}
以上代码片段断言20是否在3到20这个范围内,显然是在的.
此方法支持一个重载接收一个Icomparer参数用于自定义一个比较器,这样就可以判断任意对象是否在某一范围内,有这方面需求的同学可以研究一下
Assert.Single
用于断言集合只包含 一个
元素,这个方法有几个重载.
重载1
用于断言集合中是仅包含一个元素,这个重载可能大部分时候不是很有用
[Fact]
public void Test1()
{
Assert.Single(new[] {3});
}
重载2 接收一个参数,用于断言这个元素是否是指定元素
[Fact]
public void Test1()
{
Assert.Single(new[] {3,4,5,9},3);
}
以上代码断言集体中只有一个元素是3
此方法相当于对集合执行linq的first方法
重载3 接收一个predict类型委托,用于断言这个元素是否满足指定条件
[Fact]
public void Test1()
{
Assert.Single(new[] {3,4,5,9},a=>a>5);
}
以上方法断言集合中的这个是否只包含一个大于5的元素.
此方法相当于linq里面的single方法的有参重载
Assert.IsAssignableFrom
用于断言实例对象的类型是否是一个类型的子类(或者本身)
这个方法Nunit里也有,和反射里的
IsAssignableFrom
语义相同
Assert.IsType
用于断言实例对象的类型是否是某一指定类型
和IsAssignableFrom相比,此方法要求实例对象类型必须确切地是某一类型