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. ...
随机推荐
- 《Java从入门到放弃》JavaSE入门篇:变量
变量是什么玩意呢? 变量,顾名思义就是能变化的量 - - 好吧,举个栗子. 图片上的各种餐具,就是变量,因为同一个盘子可以在不同的时间装不同的菜,在这一桌可以装土豆肉丝,在下一桌可以装清炒黄瓜(当然, ...
- appium问题解决
ppium 1.4.16 版本 测试安卓7.0 提示AppiumSettings.Unlock.AndroidInputManager 安装 修改 C:\Program Files (x86)\App ...
- PHP设计模式_单例模式
了解 单例设计模式用于限制特定对象只能被实例化创建一次,有且只有一个此类型的资源.例如,通过数据库句柄到数据库的连接是独占的.您希望在应用程序中共享数据库句柄,因为在保持连接打开或关闭时,它是一种开销 ...
- 浅谈HashMap 的底层原理
本文整理自漫画:什么是HashMap? -小灰的文章 .已获得作者授权. HashMap 是一个用于存储Key-Value 键值对的集合,每一个键值对也叫做Entry.这些个Entry 分散存储在一个 ...
- ubuntu16.04解决tensorflow提示未编译使用SSE3、SSE4.1、SSE4.2、AVX、AVX2、FMA的问题【转】
本文转载自:https://blog.csdn.net/Nicholas_Wong/article/details/70215127 rticle/details/70215127 在我的机器上出现的 ...
- 试着用React写项目-利用Webpack搭环境
转载请注明出处:王亟亟的大牛之路 最近都蛋疼,然后前些天开了个会就是关于"不加班就得死"的死命令,作为抵制加班的先头兵,我感觉我时日无多是时候加快武装自己的速度不然吃土都不配了,就 ...
- NOIP2016 T4 魔法阵 暴力枚举+前缀和后缀和优化
想把最近几年的NOIP T4都先干掉,就大概差16年的,所以来做一做. 然后这题就浪费了我一整天QAQ...果然还是自己太弱了QAQ 点我看题 还是pa洛谷的... 题意:给m个物品,每个物品有一个不 ...
- CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none 解决方法
参考:CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none 环境 主系统 OS X,虚拟机,Ubuntu 14.04 64bit. 问题描述 ...
- POJ 1170 Shopping Offers(完全背包+哈希)
http://poj.org/problem?id=1170 题意:有n种花的数量和价格,以及m种套餐买法(套餐会便宜些),问最少要花多少钱. 思路:题目是完全背包,但这道题目不好处理的是套餐的状态, ...
- Spring Cloud组件
Spring Cloud Eureka Eureka负责服务的注册于发现,Eureka的角色和 Zookeeper差不多,都是服务的注册和发现,构成Eureka体系的包括:服务注册中心.服务提供者.服 ...