【Nunit入门系列讲座 1】Nunit的安装及功能介绍
NUnit 是一套开源的基于.NET平台的类Xunit白盒测试架构,支持所有的.NET平台。这套架构的特点是开源,使用方便,功能齐全。很适合作为.NET语言开发的产品模块的白盒测试框架。我们还可以通过扩展该套架构,形成适合我们自己的更为高级的白盒测试架构。在这个系列中,我们将从最基础的安装,部署到在实际项目中的应用,带领大家逐步揭开Nunit的面纱。
一、下载及安装
NUnit的官方主页是http://www.nunit.org/index.php?p=home,在上面我们可以找到下载的位置
doc 是相关的文档,我们这里下载msi安装包,然后开始安装
二、运行及功能介绍
在开始菜单中可以找到NUnit
运行NUnit就可以看到主界面出现
下面对各个功能区做一个简单介绍:
1. NUnit工具栏: 可以在这里执行所有的NUnit功能。主要功能有创建/打开项目,设定项目执行配置,以及为项目添加测试组件等。
2. 测试树图: 这里显示了当前NUnit项目中包含的所有测试。有两种显示方式,一种是根据在测试代码中定义的名字空间结构及测试集来显示。还有一种是根据Category显示,可以在测试代码中将同类别的测试项目定义为相同的Category,这样就可以在这种显示方式中将同种类的测试放在一起执行。
3. 测试执行:这里可以控制测试的运行及中止,并会显示当前项目的测试集执行进度。
4. 错误显示:在测试没有通过时,会显示错误原因及相关信息在这里。
5. Log窗口: 这个窗口会在测试中显示Log信息,主要有一些异常和错误信息,没有跑到的测试和测试代码的文本输出。
另外还有一个状态栏,在最下边,主要显示一下当前的运行状态及Project的Case总数。
三、 部署、生成测试
安装好NUnit后,我们就可以在我们的项目中部署他来生成我们的测试了。如何在项目中部署NUnit呢,一般白盒测试,不会改动项目功能代码,而是单独为这个测试建立一个测试项目。我们只要在这个项目中引用NUnit组建,我们就可以使用它了。下面我们来一步步生成我们的第一个测试。
在visual studio中,我们创建一个空项目,并添加NUnit组件的引用,Nunit可以加载的是dll或者exe类型的组件,我们创建该类型的项目。
为这个项目添加NUnit组件引用
这里我安装过2个以上版本的NUnit安装包,所以可以看到有2种版本的组件可以选择,大家使用的时候需要注意添加的版本是你需要的版本。关于不同版本的差异,可以参考官网上的说明。
现在将下面代码输入MyTest.cs文件,中间有些属性暂时不理解没有关系,我们在后续讲座中会详细讲解,现在让我们的第一个测试先Run起来不是一件很有成就感的事情么?开始吧!
using System;
using System.Collections;
using NUnit.Framework;
namespace MyFirstTest
{
/// <summary>
/// This is our first Nunit test
/// </summary>
[TestFixture]
public class MyTest
{
[Test]
public void Test1()
{
Console.WriteLine("Test1 Pass");
}
[Test]
public void Test2()
{
Console.WriteLine("Test2 Fail");
Assert.Fail();
}
[Test]
public void Test3()
{
Console.WriteLine("Test3 Ignore");
Assert.Ignore();
}
}
}
注意这句
using NUnit.Framework;
这句的意思是对NUnit的命名空间进行声明,我们就可以在我们的代码中使用NUnit了,这步和前面的添加引用缺一不可。
现在可以编译我们的测试工程,会生成一个叫MyFirstTest.dll的文件,这个文件就是我们的测试组件了。可以用NUnit加载并运行它。
现在,我们就可以运行我们的测试了,请看下这个测试的运行结果
这个测试中,我们定义了一个成功的测试Test1,失败的测试Test2,被跳过的测试Test3。大家可以注意看在测试集树图中,不同的测试结果显示的是不同的图标,很一目了然。在右边的错误区显示了失败的Case及一些测试的统计数据。右下角的Log区,显示了错误产生在测试的那一行,方便测试后的Debug工作。另外,大家可能注意到了,在每个测试的代码中,我们输出了一些信息。这些信息应该显示在哪里呢?对了,就是Text Output这个标签下,很方便吧,我们可以在测试代码中多输出一些方便我们观察的信息。
好了,到此为止,我们就部署并运行了我们的第一个基于NUnit的测试。是不是感觉很简单呢,另外还有点提醒大家,NUnit支持到最新的.Net Framework4。针对不同的CLR版本,我们需要不同的运行环境。首先设置我们的测试工程的CLR版本,在VS中,选择Application属性,选择想要的版本
这里我们选择的是.NET Framework 3.5,在NUnit中,我们切换运行环境到.NET Framework4(兼容3.5)就可以了。
三、 总结
本次内容就到这里,我们从安装到运行我们的第一个NUnit测试,一步步的都走了一遍,NUnit的功能当然不只这么多,后期我们将会有更多功能和技巧的介绍。
【Nunit入门系列讲座 2】Nunit的测试集管理
上一次讲座中我们初步了解了NUnit的用法,并定义了一些简单的测试(请参考http://blog.csdn.net/snowshinoy/article/details/6951352)。大家可能也注意到了,这些测试什么也不做,就是直接让测试通过或者失败。今天,我们会了解一下NUnit中最常用的一项功能——测试集管理。大家都知道,当我们的测试Case越来越多的时候,每次选择其中一些相关Case来跑会显得相当的不便,其他人也不能一眼看出我们的测试是如何分类的。通过NUnit提供的一些属性,我们可以很轻松的完成这种测试分类及分组运行的要求。
一、TestFixture 和 Test属性
NUnit的很多功能都是通过属性来提供的。属性是.NET语言中一个很重要的特性,从代码上看,属性就是一个加在元素前面的中括号标识。如下
[Ignore("ignored test")]
public void IgnoredTest()
{
throw new Exception();
}
上面代码中的第一行就是一个属性定义。从原理上说,属性是在.NET组件文件的Metadata中添加的一些可以被其他组件读取的信息。大家如果不理解,就把他想象成HTML的标签就可以。
NUnit根据测试组件的命名空间及TestFixture和Test属性来分类不同的测试。
TestFixture属性定义了一组测试,该属性加在测试类定义上,表明该类中定义的测试都是属于同一个测试集的。
Test属性定义了一个测试,该属性加在测试类成员函数定义上,表明该函数定义了一个单独的测试。
知道了这2个属性的基本概念后,我们就可以来定义我们的测试集了。这里我们定义两组测试,一组名称叫IQuickTestGp1,另一组叫IQuickTestGp2。这个2个测试分别为自动化组和白盒组所用,每个测试集里包含的测试如下:
IQuickTestGp1: AutomationTest1,AutomationTest2,AutomationTest3。
IQuickTestGp2: WhiteboxTest1,WhiteboxTest2,WhiteboxTest3.
定义测试的代码如下:
using System;
using System.Collections;
using NUnit.Framework;
namespace MyFirstTest
{
/// <summary>
/// This is our first Nunit test
/// </summary>
[TestFixture]
public class IQuickTestGp1
{
[Test]
public void AutomationTest1()
{
Console.WriteLine("AutomationTest1 Pass");
}
[Test]
public void AutomationTest2()
{
Console.WriteLine("AutomationTest2 Fail");
Assert.Fail();
}
[Test]
public void AutomationTest3()
{
Console.WriteLine("AutomationTest3 Ignore");
Assert.Ignore();
}
}
[TestFixture]
public class IQuickTestGp2
{
[Test]
public void WhiteboxTest1()
{
Console.WriteLine("WhiteboxTest1 Pass");
}
[Test]
public void WhiteboxTest2()
{
Console.WriteLine("WhiteboxTest2 Fail");
Assert.Fail();
}
[Test]
public void WhiteboxTest3()
{
Console.WriteLine("WhiteboxTest3 Ignore");
Assert.Ignore();
}
}
}
如上代码编译过后,就生成了MyFirstTest.dll,和前面一讲一样,将该测试组件加载到NUnit的Gui中,就可以看到测试已经按照我们的意愿组织成了如下形式:
是不是很整齐,漂亮呢?让我们在第一组TestFixture上来Run测试,就会发现,只有该测试集下的测试被启动了。这样我们不用再在一堆测试中找我们需要跑的测试在那里了,只要把相关测试分类到不同集合下就相当清楚。
最后再说一个技巧,其实不光通过TestFixture属性可以进行测试分类,细心的朋友可能已经注意到了,上图中的MyFirstTest也是一个节点,这个不就是我们代码的名称空间么?对了,利用名称空间一样可以进行测试集的分类,也就是把一组TestFixture放在一起。我们重新添加一个cs文件,这份文件和前面我们输入的完全一样,只要修改下Namespace定义即可
namespace MyFirstTest2
现在我们重新编译下测试,看下这次的测试集是什么样的。
测试集本身又被分类到了2大类中:MyFirstTest和MyFirstTest2。
现在我们了解了NUnit中测试集管理的基本技巧,其实NUnit还有更多的测试集管理方法,我们后续将会详细介绍。请继续关注本系列课程。
【Nunit入门系列讲座 3】NUnit断言- 如何使用断言
我们学会了定义测试及测试集后,就可以开始编写我们的测试了。至此,我们就正式进入了实战阶段了。单元测试及白盒测试在很多人眼中有一些些的神秘,很多以前听都没有听说过的名词都会在这里遇到,给人高深莫测的感觉,比如断言啊,打桩啊等等,让很多新手开始就感觉迷惑。事情真的如此复杂么?其实不是!佛家有句话,叫真水无香。真正的白盒测试,其实没有多么花哨难懂。这些名词如果真正理解了他们的含义,你一定会觉得其实真相如此简单。那么就让我们一步步的接近真相吧。
一、什么是断言?
做过手工测试的朋友一定都接触过TP(Test Precedure/Test Plan),这份文档里一个最大的特点就是每个测试步骤都会有一个期望值或者期望结果,用来判定该步测试是否通过。做自动化的朋友在写自动化脚本的时候,一定也会在一些地方设置检查点(Check point),用来决定测试是否通过,以及根据结果来在脚本中做相应处理。好了,说到这里,大家可能也猜到了我想说什么,没错!断言其实就是白盒测试中的检查点或者叫判定,英文叫Assertion。就是这么简单,这样大家应该都理解了吧。举个例子,我们在第二讲中就使用过断言,发现了么?(前文请参考http://blog.csdn.net/snowshinoy/article/details/6955813)
Assert.Fail();
这是个最最简单的断言,该断言直接让测试失败。重新看下第一篇中的3个测试
public class IQuickTestGp1
{
[Test]
public void AutomationTest1()
{
Console.WriteLine("AutomationTest1 Pass");
}
[Test]
public void AutomationTest2()
{
Console.WriteLine("AutomationTest2 Fail");
Assert.Fail();
}
[Test]
public void AutomationTest3()
{
Console.WriteLine("AutomationTest3 Ignore");
Assert.Ignore();
}
}
很清楚,Assert.Ignor()用于让测试被忽略,Assert.Fail()让测试直接失败,那如果想让测试成功呢?很简单,就是没有失败的断言测试即可成功,在这里,我们偷了个懒,压根没有放任何断言在第一个测试中,所以他自然PASS了。
二、相等/不等断言及在测试中如何使用断言
这里我们为了更好地说明,写了一个简单的计算器模块来作为待测模块。该模块由一个叫Calculate的类构成,对外提供2个方法,add()及sub(),代码如下。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IQuickTestLesson3
{
public class Calculate
{
public int add(int arg1, int arg2)
{
return arg1 + arg2;
}
public int sub(int arg1, int arg2)
{
return arg1 - arg2;
}
}
}
下面,我们就来编写测试,来测试一下这个功能模块的2个功能是否正确完成了。在此之前让我们学习一下NUnit的2种断言。
Assert.AreEqual( int expected, int actual );
Assert.AreEqual( int expected, int actual, string message );
Assert.AreEqual( int expected, int actual, string message,
params object[] parms );
Assert.AreNotEqual( int expected, int actual );
Assert.AreNotEqual( int expected, int actual, string message );
Assert.AreNotEqual( int expected, int actual, string message,
params object[] parms );
第一组断言的意思是比较expected和actual的值是否相等,如果不等,则测试失败,并且可能输出一些message以便于记录测试失败原因。
第二组断言的意思正好相反,比较expected和actural的值是否不等,如果想等,则测试失败,并且可能输出一些message以便于记录测试失败原因。
通过这2组断言,我们就可以完成对上面的计算器模块的测试。我们的想法很简单,输入2个值,看加和减的结果是不是正确即可。这里我们输入9和3,来测试add功能,可以预想,如果这个功能实现正确那当我们输入9和3时,add给出的结果应该是12。而当我们输入9和2时,add的结果应该不是12。另外我们再输入10和2,来测试sub功能。功能实现正确时,我们可以得到8的结果,而我们故意输入10和0,得到的结果应该不是8。这样的测试想法是不是很合情合理呢?让我们使用断言来表达。
Assert.AreEqual(expected,calculate.add(9, 3) );
Assert.AreNotEqual(expected,calculate.add(9, 2) );
Assert.AreEqual(expected,calculate.sub(10, 2));
Assert.AreNotEqual(expected,calculate.sub(10, 0) );
至此,我们的所有测试的准备工作都做完了,想好了测试用例。下面就是正式将测试模块编写完成了。
我们在Visual Studio中新建一个用来测试Calculate模块的工程,叫CalculateTest,并添加对Nunit.Framework及待测模块的引用(这种模块就叫做测试驱动模块,用来驱动测试进行)。整个测试模块的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
namespace IQuickTestLesson3
{
[TestFixture]
public class CalculateTest
{
[Test]
public void AddTest()
{
int expected = 12;
Calculate calculate = new Calculate();
Assert.AreEqual(expected,calculate.add(9, 3) );
Assert.AreNotEqual(expected,calculate.add(9, 2) );
}
[Test]
public void SubTest()
{
int expected = 8;
Calculate calculate = new Calculate();
Assert.AreEqual(expected,calculate.sub(10, 2));
Assert.AreNotEqual(expected,calculate.sub(10, 0) );
}
}
}
2个工程在Visual Studio中如图所示:
编译整个Solution后,就得到了待测模块及测试模块2个dll。将测试模块CalculateTest.dll加载到NUnit中,并运行
可以看到我们定义的2个测试都通过了,这是因为我们的add和sub功能确实没有什么大的问题(目前而言,后面我会告诉大家,其实问题很大)。让我们从测试成功的喜悦中清醒下,如果我们的待测模块失败了呢?这个才应该是我们在学习中最应该关心的,不然不知道失败怎么处理的测试又有什么用处呢。让我们来冒充一个粗心的开发,看看测试模块会给我们什么样的惊喜。将Calculate的add功能代码修改如下,然后重新编译该工程。
public int add(int arg1, int arg2)
{
return arg1 - arg2;
}
你信么?虽然看起来很愚蠢,但是这样的错误确实是可能发生的。特别你在写上几十个实现同一功能的重载函数的时候,你会忘记你正在实现的其实是什么了(假设你还不知道泛型这种东西)。好了,让我们重新运行一次测试,注意,我们没有改动任何测试代码。
add功能的测试失败了。还不仅仅是这样,NUnit告诉了我们更多。让我们注意看下错误提示和LOG。
??IQuickTestLesson3.CalculateTest.AddTest:
Expected: 12
But was: 6
期望值和实际值不符合,期望得到的是12,实际确是6。
at IQuickTestLesson3.CalculateTest.AddTest() in C:\DATA\Users\19006418\My Documents\Visual Studio 2008\Projects\WIN32\Calculate\CalculateTest\CalculateTest.cs:line 17
测试失败在第17行,这样的LOG有利于我们回头检查失败点,有些时候,测试的失败也不一定是待测模块的BUG,也可能是测试模块本身的设计问题。失败后,需要先确保测试设计及编码无误。这样的错误信息,可以让我们很方便的跟踪到错误的发生地,便于DEBUG。如果你还觉得这样的信息不够,NUnit的断言也允许你添加自己的错误信息。如下我们输出更多的信息,来看看效果。
public void AddTest()
{
int expected = 12;
Calculate calculate = new Calculate();
Assert.AreEqual(expected,calculate.add(9, 3),"we test add with 9 and 3,should be 12,if actual 6,maybe + is inputed as -" );
Assert.AreNotEqual(expected,calculate.add(9, 2) );
}
这次,我们在断言里加上了很长的一段信息,甚至都猜测到可能发生错误的原因了,那么这些信息会帮助到我们么?运行测试看下。
这次再看下错误信息,是不是智能多了。通过这种方式,我们可以极大的丰富我们的错误信息,保证测试的可读性。
三、说些多余的话
其实至此,本节课程所说的基本都说完了。但是我还是想多说些和本节主题无关的话,权当结语吧。这样的一个测试,简单而有效。但是,他完整么?还记得我们前面说过一句,目前而言,我们的这个add和sub实现,没有什么大的问题。目前2个字含义何在。可能有些朋友已经想到了,对的,这2个功能只适用于Int类型的参数。这个我们可以在测试中发现么?其实是可以的。如果我们不是这2个功能开发者,并且预期他应该支持所有可以进行算术运算类型的参数。那么我们就应该有这样的测试存在。
Assert.AreNotEqual(expected,calculate.add(9.1, 2.3) );
那这样的测试会导致什么样的结果呢?我们修改完测试模块再编译一下,就会发现。根本无法通过了。
这样我们的测试也算是失败了。所以,我们进行白盒测试的时候,不要只着眼于待测模块的代码,而是应该着眼于设计。设计要求什么样,而不是功能的代码写成了什么样。更进一步,我们甚至可以在功能代码没有完成前,就进行测试代码的编写(TDD的思想)。
这次的内容就到这里,再次特别感谢下Joe Satriani,Steve Vai和Yngwie Malmsteem,是你们的音乐让我在深夜中充满激情的写完了这篇。还是那句老话,请继续关注本系列课程。
|