NUnit使用
NUnit是.net平台上使用得最为广泛的测试框架之一,本文将通过示例来描述NUnit的使用方法,并提供若干编写单元测试的建议和技巧,供单元测试的初学者参考。
继续下文之前,先来看看一个非常简单的测试用例(TestCase):
- [Test]
- public void AdditionTest()
- {
- int expectedResult = 2;
- Assert.AreEqual(exptectedResult, 1 + 1);
- }
你肯定会说这个TestCase也太白痴了吧!这也是许多NUnit文档被人诟病的一点,但是我的理解并不是这样,xUnit本来就是编写UT的简易框架,keep it simple and stupid,任何通过复杂的TestCase来介绍NUnit的用法都是一种误导,UT复杂之处在于如何在实际项目中应用和实施,而不是徘徊于该如何使用NUnit。
主要内容:
1、NUnit的基本用法
2、测试用例的组织
3、NUnit的断言(Assert)
4、常用单元测试工具介绍
一、NUnit的基本用法
和其他xNUnit框架不同的是,NUnit框架使用Attribute(如前面代码中的[Test])来描述测试用例的,也就是说我们只要掌握了 Attribute的用法,也就基本学会如何使用NUnit了。VSTS所集成的单元测试也支持类似NUnit的Attributes,下表对比了 NUnit和VSTS的标记:
usage |
NUnit attributes |
VSTS attributes |
标识测试类 |
TestFixture |
TestClass |
标识测试用例(TestCase) |
Test |
TestMethod |
标识测试类初始化函数 |
TestFixtureSetup |
ClassInitialize |
标识测试类资源释放函数 |
TestFixtureTearDown |
ClassCleanup |
标识测试用例初始化函数 |
Setup |
TestInitialize |
标识测试用例资源释放函数 |
TearDown |
TestCleanUp |
标识测试用例说明 |
N/A |
Description |
标识忽略该测试用例 |
Ignore |
Ignore |
标识该用例所期望抛出的异常 |
ExpectedException |
ExpectedException |
标识测试用例是否需要显式执行 |
Explicit |
? |
标识测试用例的分类 |
Category |
? |
现在,让我们找一个场景,通过示例来了解上述NUnit标记的用法。来看看一个存储在数据库中的数字类:
这是我们常见的DAL+Entity的设计,DigitDataProvider和Digit类的实现代码如下:
1)Digit.cs类:
- using System;
- using System.Data;
- namespace Product
- {
- /// <summary>
- /// Digit 的摘要说明
- /// </summary>
- /// 创 建 人: Aero
- /// 创建日期: 2006-3-22
- /// 修 改 人:
- /// 修改日期:
- /// 修改内容:
- /// 版 本:
- public class Digit
- {
- private Guid _digitId;
- public Guid DigitID
- {
- get { return this._digitId; }
- set { this._digitId = value; }
- }
- private int _value = 0;
- public int Value
- {
- get { return this._value; }
- set { this._value = value; }
- }
- #region 构造函数
- /// <summary>
- /// 默认无参构造函数
- /// </summary>
- /// 创 建 人: Aero
- /// 创建日期: 2006-3-22
- /// 修 改 人:
- /// 修改日期:
- /// 修改内容:
- public Digit()
- {
- //
- // TODO: 在此处添加构造函数逻辑
- //
- }
- /// <summary>
- /// construct the digit object from a datarow
- /// </summary>
- /// <param name="row"></param>
- public Digit(DataRow row)
- {
- if (row == null)
- {
- throw new ArgumentNullException();
- }
- if (row["DigitID"] != DBNull.Value)
- {
- this._digitId = new Guid(row["DigitID"].ToString());
- }
- if (row["Value"] != DBNull.Value)
- {
- this._value = Convert.ToInt32(row["Value"]);
- }
- }
- #endregion
- }
- }
2)DigitDataProvider类:
- using System;
- using System.Data;
- using System.Data.SqlClient;
- using System.Collections;
- namespace Product
- {
- /// <summary>
- /// DigitDataProvider 的摘要说明
- /// </summary>
- /// 创 建 人: Aero
- /// 创建日期: 2006-3-22
- /// 修 改 人:
- /// 修改日期:
- /// 修改内容:
- /// 版 本:
- public class DigitDataProvider
- {
- /// <summary>
- /// 定义数据库连接
- /// </summary>
- private SqlConnection _dbConn;
- public SqlConnection Connection
- {
- get { return this._dbConn; }
- set { this._dbConn = value; }
- }
- #region 构造函数
- /// <summary>
- /// 默认无参构造函数
- /// </summary>
- /// 创 建 人: Aero
- /// 创建日期: 2006-3-22
- /// 修 改 人:
- /// 修改日期:
- /// 修改内容:
- public DigitDataProvider()
- {
- //
- // TODO: 在此处添加构造函数逻辑
- //
- }
- public DigitDataProvider(SqlConnection conn)
- {
- this._dbConn = conn;
- }
- #endregion
- #region 成员函数定义
- /// <summary>
- /// retrieve all Digits in the database
- /// </summary>
- /// <returns></returns>
- public ArrayList GetAllDigits()
- {
- // retrieve all digit record in database
- SqlCommand command = this._dbConn.CreateCommand();
- command.CommandText = "SELECT * FROM digits";
- SqlDataAdapter adapter = new SqlDataAdapter(command);
- DataSet results = new DataSet();
- adapter.Fill(results);
- // convert rows to digits collection
- ArrayList digits = null;
- if (results != null && results.Tables.Count > 0)
- {
- DataTable table = results.Tables[0];
- digits = new ArrayList(table.Rows.Count);
- foreach (DataRow row in table.Rows)
- {
- digits.Add(new Digit(row));
- }
- }
- return digits;
- }
- /// <summary>
- /// remove all digits from the database
- /// </summary>
- /// <returns></returns>
- public int RemoveAllDigits()
- {
- // retrieve all digit record in database
- SqlCommand command = this._dbConn.CreateCommand();
- command.CommandText = "DELETE FROM digits";
- return command.ExecuteNonQuery();
- }
- /// <summary>
- /// retrieve and return the entity of given value
- /// </summary>
- /// <exception cref="System.NullReferenceException">entity not exist in the database</exception>
- /// <param name="value"></param>
- /// <returns></returns>
- public Digit GetDigit(int value)
- {
- // retrieve entity of given value
- SqlCommand command = this._dbConn.CreateCommand();
- command.CommandText = "SELECT * FROM digits WHERE Value='" + value + "'";
- SqlDataAdapter adapter = new SqlDataAdapter(command);
- DataSet results = new DataSet();
- adapter.Fill(results);
- // convert rows to digits collection
- Digit digit = null;
- if (results != null && results.Tables.Count > 0
- && results.Tables[0].Rows.Count > 0)
- {
- digit = new Digit(results.Tables[0].Rows[0]);
- }
- else
- {
- throw new NullReferenceException("not exists entity of given value");
- }
- return digit;
- }
- /// <summary>
- /// remove prime digits from database
- /// </summary>
- /// <returns></returns>
- public int RemovePrimeDigits()
- {
- throw new NotImplementedException();
- }
- #endregion
- }
- }
3)新建测试数据库:
- CREATE TABLE [dbo].[digits] (
- [DigitID] [uniqueidentifier] NOT NULL ,
- [Value] [int] NOT NULL
- ) ON [PRIMARY]
- GO
下面,我们开始尝试为DigitDataProvider类编写UT,新建DigitDataProviderTest.cs类。
1、添加nunit.framework引用:
并在DigitDataProviderTest.cs中添加:
2、编写测试用例
1)标识测试类:NUnit要求每个测试类都必须添加TestFixture的Attribute,并且携带一个public无参构造函数。
- [TestFixture]
- public class DigitProviderTest
- {
- public DigitProviderTest()
- {
- }
- }
2)编写DigitDataProvider.GetAllDigits()的测试函数
- /// <summary>
- /// regular test of DigitDataProvider.GetAllDigits()
- /// </summary>
- [Test]
- public void TestGetAllDigits()
- {
- // initialize connection to the database
- // note: change connection string to ur env
- IDbConnection conn = new SqlConnection(
- "Data source=localhost;user id=sa;password=sa;database=utdemo");
- conn.Open();
- // preparing test data
- IDbCommand command = conn.CreateCommand();
- string commadTextFormat = "INSERT INTO digits(DigitID, Value) VALUES('{0}', '{1}')";
- for (int i = 1; i <= 100; i++)
- {
- command.CommandText = string.Format(commadTextFormat, Guid.NewGuid().ToString(), i.ToString());
- command.ExecuteNonQuery();
- }
- // test DigitDataProvider.GetAllDigits()
- int expectedCount = 100;
- DigitDataProvider provider = new DigitDataProvider(conn as SqlConnection);
- IList results = provider.GetAllDigits();
- // that works?
- Assert.IsNotNull(results);
- Assert.AreEqual(expectedCount, results.Count);
- // delete test data
- command = conn.CreateCommand();
- command.CommandText = "DELETE FROM digits";
- command.ExecuteNonQuery();
- // close connection to the database
- conn.Close();
- }
什么?很丑?很麻烦?这个问题稍后再讨论,先来看看一个完整的测试用例该如何定义:
- [Test]
- public void TestCase()
- {
- // 1) initialize test environement, like database connection
- // 2) prepare test data, if neccessary
- // 3) test the production code by using assertion or Mocks.
- // 4) clear test data
- // 5) reset the environment
- }
NUnit要求每一个测试函数都可以独立运行(往往有人会误解NUnit并按照Consoler中的排序来执行),这就要求我们在调用目标函数之前先要初始化目标函数执行所需要的环境,如打开数据库连接、添加测试数据等。为了不影响其他的测试函数,在调用完目标函数后,该测试函数还要负责还原初始环境,如删除测试数据和关闭数据库连接等。对于同一测试类里的测试函数来说,这些操作往往是相同的,让我们对上面的代码进行一次Refactoring, Extract Method:
- /// <summary>
- /// connection to database
- /// </summary>
- private static IDbConnection _conn;
- /// <summary>
- /// 初始化测试类所需资源
- /// </summary>
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- // note: change connection string to ur env
- DigitProviderTest._conn = new SqlConnection(
- "Data source=localhost;user id=sa;password=sa;database=utdemo");
- DigitProviderTest._conn.Open();
- }
- /// <summary>
- /// 释放测试类所占用资源
- /// </summary>
- [TestFixtureTearDown]
- public void ClassCleanUp()
- {
- DigitProviderTest._conn.Close();
- }
- /// <summary>
- /// 初始化测试函数所需资源
- /// </summary>
- [SetUp]
- public void TestInitialize()
- {
- // add some test data
- IDbCommand command = DigitProviderTest._conn.CreateCommand();
- string commadTextFormat = "INSERT INTO digits(DigitID, Value) VALUES('{0}', '{1}')";
- for (int i = 1; i <= 100; i++)
- {
- command.CommandText = string.Format(
- commadTextFormat, Guid.NewGuid().ToString(), i.ToString());
- command.ExecuteNonQuery();
- }
- }
- /// <summary>
- /// 释放测试函数所需资源
- /// </summary>
- [TearDown]
- public void TestCleanUp()
- {
- // delete all test data
- IDbCommand command = DigitProviderTest._conn.CreateCommand();
- command.CommandText = "DELETE FROM digits";
- command.ExecuteNonQuery();
- }
- /// <summary>
- /// regular test of DigitDataProvider.GetAllDigits()
- /// </summary>
- [Test]
- public void TestGetAllDigits()
- {
- int expectedCount = 100;
- DigitDataProvider provider =
- new DigitDataProvider(DigitProviderTest._conn as SqlConnection);
- IList results = provider.GetAllDigits();
- // that works?
- Assert.IsNotNull(results);
- Assert.AreEqual(expectedCount, results.Count);
- }
NUnit提供了以下Attribute来支持测试函数的初始化:
TestFixtureSetup:在当前测试类中的所有测试函数运行前调用;
TestFixtureTearDown:在当前测试类的所有测试函数运行完毕后调用;
Setup:在当前测试类的每一个测试函数运行前调用;
TearDown:在当前测试类的每一个测试函数运行后调用。
3)编写DigitDataProvider.RemovePrimeDigits()的测试函数
唉,又忘了质数判断的算法,这个函数先不实现(throw new NotImplementedException()),对应的测试函数先忽略。
- /// <summary>
- /// regular test of DigitDataProvider.RemovePrimeDigits
- /// </summary>
- [Test, Ignore("Not Implemented")]
- public void TestRemovePrimeDigits()
- {
- DigitDataProvider provider =
- new DigitDataProvider(DigitProviderTest._conn as SqlConnection);
- provider.RemovePrimeDigits();
- }
Ignore的用法:
4)编写DigitDataProvider.GetDigit()的测试函数
当查找一个不存在的Digit实体时,GetDigit()会不会像我们预期一样抛出NullReferenceExceptioin呢?
- /// <summary>
- /// Exception test of DigitDataProvider.GetDigit()
- /// </summary>
- [Test, ExpectedException(typeof(NullReferenceException))]
- public void TestGetDigit()
- {
- int expectedValue = 999;
- DigitDataProvider provider = new DigitDataProvider(DigitProviderTest._conn as SqlConnection);
- Digit digit = provider.GetDigit(expectedValue);
- }
ExpectedException的用法
ExpectedException(Type t, string expectedMessage)
在NUnitConsoler里执行一把,欣赏一下黄绿灯吧。本文相关代码可从UTDemo_Product.rar下载。
二、测试函数的组织
现在有一个性能测试的Testcase,执行一次要花上一个小时,我们并不需要(也无法忍受)每次自动化测试时都去执行这样的Testcase,使用NUnit的Explicit标记可以让这个TestCase只有在显示调用下才会执行:
- [Test, Explicit]
- public void OneHourTest()
- {
- //
- }
不幸的是,这样耗时的TestCase在整个测试工程中可能有数十个,或许更多,我们能不能把这些TestCase都组织起来,要么一起运行,要么不运行呢?NUnit提供的Category标记可实现此功能:
- [Test, Explicit, Category("LongTest")]
- public void OneHourTest()
- {
- ...
- }
- [Test, Explicit, Category("LongTest")]
- public void TwoHoursTest()
- {
- ...
- }
这样,只有当显示选中LongTest分类时,这些TestCase才会执行
三、NUnit的断言
NUnit提供了一个断言类NUnit.Framework.Assert,可用来进行简单的state base test(见idior的Enterprise Test Driven Develop),可别对这个断言类期望太高,在实际使用中,我们往往需要自己编写一些高级断言。
常用的NUnit断言有:
method |
usage |
example |
Assert.AreEqual(object expected, object actual[, string message]) |
验证两个对象是否相等 |
Assert.AreEqual(2, 1+1) |
Assert.AreSame(object expected, object actual[, string message]) |
验证两个引用是否指向同意对象 |
object expected = new object(); object actual = expected; Assert.AreSame(expected, actual) |
Assert.IsFalse(bool) |
验证bool值是否为false |
Assert.IsFalse(false) |
Assert.IsTrue(bool) |
验证bool值是否为true |
Assert.IsTrue(true) |
Assert.IsNotNull(object) |
验证对象是否不为null |
Assert.IsNotNull(new object()) |
Assert.IsNull(object) |
验证对象是否为null |
Assert.IsNull(null); |
这里要特殊指出的Assert.AreEqual只能处理基本数据类型和实现了Object.Equals接口的对象的比较,对于我们自定义对象的比较,通常需要自己编写高级断言,这个问题郁闷了我好一会,下面给出一个用于level=1的情况下的对象比较的高级断言的实现:
- public class AdvanceAssert
- {
- /// <summary>
- /// 验证两个对象的属性值是否相等
- /// </summary>
- /// <remarks>
- /// 目前只支持的属性深度为1层
- /// </remarks>
- public static void AreObjectsEqual(object expected, object actual)
- {
- // 若为相同引用,则通过验证
- if (expected == actual)
- {
- return;
- }
- // 判断类型是否相同
- Assert.AreEqual(expected.GetType(), actual.GetType());
- // 测试属性是否相等
- Type t = expected.GetType();
- PropertyInfo[] properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
- foreach (PropertyInfo property in properties)
- {
- object obj1 = t.InvokeMember(property.Name,
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
- null, expected, null);
- object obj2 = t.InvokeMember(property.Name,
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
- null, actual, null);
- // 判断属性是否相等
- AdvanceAssert.AreEqual(obj1, obj2, "assertion failed on " + property.Name);
- }
- }
- /// <summary>
- /// 验证对象是否相等
- /// </summary>
- private static void AreEqual(object expected, object actual, string message)
- {
- Type t = expected.GetType();
- if (t.Equals(typeof(System.DateTime)))
- {
- Assert.AreEqual(expected.ToString(), actual.ToString(), message);
- }
- else
- {
- // 默认使用NUnit的断言
- Assert.AreEqual(expected, actual, message);
- }
- }
- }
四、常用单元测试工具介绍:
1、NUnit:目前最高版本为2.2.7(也是本文所使用的NUnit的版本)
下载地址:http://www.nunit.org
2、TestDriven.Net:一款把NUnit和VS IDE集成的插件
下载地址:http://www.testdriven.net/
3、NUnit2Report:和nant结合生成单元测试报告
下载地址:http://nunit2report.sourceforge.net
4、Rhino Mocks 2:个人认为时.net框架下最好的mocks库,而且支持.net 2.0, rocks~!
下载地址:http://www.ayende.com/projects/rhino-mocks.aspx
出处:http://blog.csdn.net/fogle/article/details/5690510
NUnit使用的更多相关文章
- 使用NUnit为游戏项目编写高质量单元测试的思考
0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...
- 舍弃Nunit拥抱Xunit
前言 今天与同事在讨论.Net下测试框架的时候,说到NUnit等大多数测试框架的SetUp以及TearDown方法并不是显得那么完美,所以在公司内部的项目中采用了Xunit框架.那么究竟是什么样的原因 ...
- Comparing the MSTest and Nunit Frameworks
I haven't seen much information online comparing the similarities and differences between the Nunit ...
- Mono 3.2 上跑NUnit测试
NUnit是一款堪与JUnit齐名的开源的回归测试框架,供.net开发人员做单元测试之用,可以从www.nunit.org网站上免费获得,最新版本是2.5.Mono 3.2 源码安装的,在/usr/b ...
- (转)对比MS Test与NUnit Test框架
前言: 项目中进行Unit Test时,肯定会用到框架,因为这样能够更快捷.方便的进行测试. .Net环境下的测试框架非常多,在这里只是对MS Test和NUnit Test进行一下比较, 因为这两个 ...
- Nunit工具做C#的单元测试
Nunit工具做C#的单元测试 学习心得 编写人:罗旭成 时间:2013年9月2日星期一 1.开发人员如何做单元测试 单元测试是针对最小的可测试软件元素(单元)的,它所测试的内容包括单元的内部结构 ...
- C# ~ NUnit单元测试
单元测试 单元测试(Unit Test)的一个测试用例(Test Case)是一小段代码,用于测试一个小的程序功能的行为是否正常,保证开发的功能子项能正确完成并实现其基本功能.一个单元测试是用于判断某 ...
- 单元测试与Nunit的基本使用
一.单元测试是什么 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证.对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,C# ...
- 对比MS Test与NUnit Test框架
前言: 项目中进行Unit Test时,肯定会用到框架,因为这样能够更快捷.方便的进行测试. .Net环境下的测试框架非常多,在这里只是对MS Test和NUnit Test进行一下比较, 因为这两个 ...
- 实现VS2010整合NUnit进行单元测试(转载)
代码编写,单元测试必不可少,简单谈谈Nunit进行单元测试的使用方式: 1.下载安装NUnit(最新win版本为NUnit-2.6.4.msi) http://www.nunit.org/index. ...
随机推荐
- pycharm添加git ignore
pycharm现在提供了git ignore,很方便 从这里下载扩展 https://plugins.jetbrains.com/plugin/7495--ignore 放到pycharm根目录\pl ...
- lombok常见注解
一.使用lombok简化代码 lombok提供了很多注解,在编译时候生成java代码,代替了手工编写一些简单的代码,使程序员可以关注更重要的实现. 二.常用注解 以model为例 public cla ...
- 使用 v-cloak 防止页面加载时出现 vue.js 的变量名
知识点:使用 v-cloak 防止页面加载时出现 vue.js 的变量名 场景:在使用vue语法,实现下拉框功能时,展示数据列表之前,出现对应的 vuejs 变量名 代码: var vm = new ...
- Gym 101334F Feel Good
http://codeforces.com/gym/101334 题意:给定一串数,求一个区间,使得该区间的所有数之和乘以该区间内最小的数的乘积最大. 思路:先预处理一下,计算出前缀和. 我们可以把每 ...
- Angular4笔记——表单状态相关的属性
表单状态字段(FromControl)touched和untouched用来判断用户是否访问过一个字段(也就是这个字段是否获取过焦点,如果获取过焦点,touched是true,untouched是fa ...
- css输入框的圆角
转载:http://jingyan.baidu.com/article/73c3ce28f0b38fe50343d926.html 1.原理是四张圆角的图片放在四个角上,就是圆角矩形的四个角,但这种方 ...
- Miller_Rabin(米勒拉宾)素数测试
2018-03-12 17:22:48 米勒-拉宾素性检验是一种素数判定法则,利用随机化算法判断一个数是合数还是可能是素数.卡内基梅隆大学的计算机系教授Gary Lee Miller首先提出了基于广义 ...
- Spring4 MVC文件下载实例(javaconfig)
展示如何使用Spring MVC4执行文件下载,我们将看到应用程序从文件系统内部以及外部文件下载文件. 下载文件是相当简单的,涉及以下步骤. 创建一个InputStream到文件用于下载. 查找MIM ...
- 80端口未被占用,无法启动wamp的解决方法(原创)
起床之后想要弄弄侧边栏的东西,打开wamp居然无法启动apache服务,上网查了之后才知道是需要启动httpd.exe这个程序,测试了很久,80端口也没有被占用,点击启动apache服务的时候弹出co ...
- linux利用软件raid搭建iscsi存储
分区:parted /dev/sdbmklabel gptmkpart primary ext4 0% 100%set 1 raid mdadm -Cv /dev/md0 -n 4 -l5 /dev/ ...