Nunit测试基础之简单断言
在开始本篇之前需要补充一些内容,通过前面搭建Nunit测试环境我们知道要使一个方法成为单元测试方法首先要在此方法所在类加上TestFixture注解,并且在该方法上添加上Test注解.
然而还有一点需要注意:所有进行单元测试的方法必须标识为public访问级别,否则无法识别为单元测试方法
此外,单元测试方法还有以下特征
- 单元测试方法不带返回参数,也即都是Void类型
由于单元测试方法都是用来断言特定状态的,因此返回值是没有意义的.此外也不要尝试在一个单元测试方法中调用另一个,这样做违反的单元测试的初衷(一旦出现错误不知道是哪个方法出出现的,还需要借助单元调用去发现是哪个方法出现的) 单元测试方法不能带有参数
这里说的不能是指不能像普通方法一样带有普通的参数(可以带基于注解的特殊参数)
如果像普通方法一样带参数,虽然编译能通过,但是运行时会抛出异常.单元测试方法不能重载
这里说的不能是不应该,实践中是可以的,但是重载方法会带来无尽的麻烦,读者可以自己实践一下.
进行单元测试无非就是对不同参数引起方法出现不同结果的断言(一般情况下所有的单元测试方法都有断言)下面我们来看Nunit中最基本最常用的断言
基本断言
Assert.True()
Assert.True用于断言布尔参数是否为true
Assert.True的重载方法还支持可空布尔参数
Assert.True还支持自定义错误提示
上面代码改为如下
如果返回错误的时候,我们自定义的错误信息就会显示出来.
其它的断言方法也大都有此重载
Assert.IsTrue
此断言方法为Assert.True的亲兄弟,二者功能一模一样.
ssert.False
与Assert.True断言状态相反,断言某一参数的结果为false
这里需要特别说明的是,单元测试应该力求简单,明了,断言尤其如此.
上面的断言还也可以写成
Assert.False(!firstCondition);
这和断言变量firstCondition为true最终功能一样,但是看上去很不直接明了,通常情况下我见到Assert.False第一反应就是断言一个变量为False,这里则反其道行之,实际上是断言一个变量为true,这种情况应当避免.
Assert.IsFalse
Assert.False的亲兄弟,二者表现一模一样
Nunit Assert类还有还多其它的前面带有Is的方法,它们都和不带Is的一模一样,其中带Is的是为了兼容老版本写法.
Assert.Null
用于断言一个变量是否为null,这里不再举例,但是实际中用的却比较多.
Assert.NotNull
用于断言一个变量不是null,它和Assert.Null()功能相同,只是断言的状态相反.
Nunit里还有其它的前缀有Not的方法,它和不带Not的方法用法一样,只是断言的状态相反
Assert.Throws
用于断言特定方法在运行的时候会抛出异常.此方法有泛型版本,异步版本,这里仅对异步版本进行说明
由于示例越来越复制,我们不能只在测试方法内写一些简单代码进行测试了,这里我们新建一个Person类如下
这个类里面包含一个WhetherNameContainsB方法,用于判断实例的Name是否包含字母B,
这个方法里面有三个逻辑分支,单元测试的时候每一个都要覆盖到,这里我们断言如果name为null则抛出ArgumentNullException
我们编写如下单元测试方法
运行这个测试,则会返回成功状态,因为预期的异常出现了.
Assert.IsEmpty
用于断言字段串是否为空字符串.
Assert.Positive
用于断言数字类型(int,long,float,double,decimal等)为正数(大于零的数)
其实很多断言都可以断言都可以用Assert.True来完成,比如断言一个数是否为正数,可以用Assert.True(a>0),这里由于a只是一个普通变量,使用a>0作为条件主义仍然十分清析,然而到了后面有我们不仅要判断一个变通变量,还要判断lambda表达式,如果条件过于复杂,则语义会变得不是特别清析了,使用Assert自带的静态方法主义会更加清析,可读性更高.
Assert.Negative
用于断言数字类型为负数(小于零,不包括零)
Assert.Zero
用于断言数字类型为数字零
Assert.NotZero
用于断言数字类型不是零.
很多时候,Not包含的范围非常广,进行单元测试是为了在开发阶段找出问题,解决问题,因此断言的范围越窄越好,我们不能仅仅让单元测试通过了事.
比如一个方法返回的结果是数字类型,我们要断定它是正数?大于某一个数的正数?在一定范围的正数?是一个具体的正数?而不能简单的是零,不是零.当然这还要根据业务本身来确实,有些时候范围可能确实很大,但是一定要注意单元测试原则.
Assert.Greater(OrEqual)
用于断言数字类型的变量大于(或者等于)某一个值
Assert.Less(OrEqual)
用于断言数字类型小于(或者等于)某一值
Assert.Contains
用于断言集合中是否包含某一元素.
比如以下方法,用于断言字符串数组中是否包含特定字符串
Assert.AreSame
用于断言两个对象是否相等
这个静态方法并没有提供重载参数用于指定一个比较器来比较引用对象的相等性,需要实现equals和gethashcode方法才能得到预期结果,但在实际中我们往往把比较器放在类外边,如何在比较引用对象的时候加载一个比较器在后面章节会有介绍,这里先略过.
Nunit测试基础之复杂断言
Nunit测试基础之复杂断言
上面一篇我们讲解了一些基本断言,利用这些断言我们就可以进行单元测试了,然而仅仅使用简单断言还是不够的,如果逻辑复杂度较高,使用简单的断言会导致单元测试代码量增加,最终导致单元测试本身过于复杂和难以维护.需要说明的是这里所说的复杂断言仍然在Assert的静态方法里面,本身也不是特别复杂,只是比前面讲的秒复杂一些,只是如果没有了这些方法,一些特殊功能实现起来比较费劲基本无法实现.
下面就介绍一下这些方法.
Assert.Catch
Assert.Catch有泛型和异步方法,这里只介绍其泛型方法.很多即使经常使用单元测试功能的人也未必用过这个方法.
其实这个方法和Assert.Throw用法上类似,只是有一点不同的是要测试的方法里的异常可以是catch到的异常的子类,实际开发中,如果我们能确立异常的类型,则最好捕获具体类型异常,然而不能排除有一些不够规范的代码整段代码被一个try catch包围,这时候不一定能够捕获到想要的特定异常,这时候可以使用Assert.Catch
以上代码类似上一节中讲throw时使用的代码,只是这里泛型参数里是Exception而不是具体的异常信息,我们运行这段代码,依然能够测试通过.
在单元测试中,期待的状态越具体越好,然而由于种种原因(比如立项时候没有对代码规范做过多要求,开发者水平不高,要测试的代码是别人写的,写单元测试的人对其中逻辑并不是特别清楚等)我们无法做到非常具体,这个时候可以把要获得的状态放宽以后,待条件完备了再修改单元测试以进一步收窄状态.
Assert.Ignore
Assert.Ignore和Ignore注解功能类似,可以在测试的时候忽略一个单元测试.有些情况下我们需要暂时忽略一个测试,比如说要进行测试的内容有一个外部依赖,现在外部依赖暂时不可用,如果我们不忽略的话测试将会失败,在自动化环境下,失败将导致无法进行下一步动作,此时我们可以暂时忽略这个测试.
忽略的测试前面有一个 黄色叹号标志,警示我们需要注意.
Assert. Fail
我们先看一下面一段代码
在这个单元测试本身使用到了try catch,我们知道WhetherNameContainsB方法在Person类的Name没有提供值的情况下会抛出异常,然而我们的代码并没有断言这个异常存在,此时由于catch代码块存在,会把异常吞掉,因此最终我们断言person的Age为正数的时候将会通过(我们在构造类的时候设置了Age为32)
这显然不行的,这时候我把们Assert.Fail(e.Message)取消注释,测试便会变成失败状态.
Assert.IsNaN
用于断言一个Double类型数字是否是NaN
虽然实际业务中我们并不会写以上代码,但是如果除数和被除数是通过复杂计算得来的则有可能除数和被除数都是零.
Assert.IsInstanceOf
用于断言一个对象是否是指定类型的实例,
如上psn是Person类的一个实例,而Person继承自Object,因此psn也是Object类的实例
Assert.IsAssignableFrom
此方法和以上方法作用相反,它用来断言指定类型是当前对象类型的子类.(Assert.IsInstanceOf判断的是当前对象是指定类型的子类)
这个方法语义不是很明确,很容易搞晕,使用的时候需要特别注意
Assert.Warn
用于使一个测试通过,但是出现警示信息.