最近在看.net单元测试艺术,我也喜欢单元测试,今天介绍一下如何测试异常、如何测试返回值、如何测试模拟对象的参数传递、如何测试数据库访问代码。单元测试框架使用的是NUnit,模拟框架使用的是:Rhino.Mocks。
1.测试异常,可以直接对方法进行异常测试,也可以对模拟对象进行异常测试,但是,对模拟对象进行异常测试,很少用,所以,这里就介绍对方法的异常测试。请看如下代码,当用户名为空的时候,抛出异常。
1
2
3
4
5
6
7
8
9
10
|
public
bool
Valid( string
userName, string
passWord)
{
if
( string .IsNullOrEmpty(userName)) throw
new
ArgumentNullException( "userName is null" );
var
isValid = userName == "admin"
&& passWord == "123456" ;
Log.Write(userName);
return
isValid;
}
|
测试代码如下:
1
2
3
4
5
6
7
8
|
[Test] [ExpectedException( typeof (ArgumentNullException))]
public
void
Vaild_Throw_Test()
{
MyLogin l = new
MyLogin();
l.Valid( "" , "123456" );
}
|
这里需要特性“ExpectedException”,意思是期待抛出一个异常。这里不需要再进行断言了,抛出异常后,后面的代码也就不会执行了。
2.测试返回值,这里主要讲测试模拟对象的返回值。这里用到了一个LastCall的一个类,比较常用,一些辅助测试的功能,都在该类中。测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
[Test] public
void
Valid_Return()
{ MockRepository mock = new
MockRepository();
var
log = mock.DynamicMock<ILog>();
using
(mock.Record())
{
log.WriteLog( "admin" );
LastCall.Return(0);
}
var
returnValue = log.WriteLog( "admin" );
Assert.AreEqual(returnValue, 0); } |
3.测试模拟对象的参数传递,当调用模拟对象时,可能需要传递参数,如果参数的值不一至时,会导致测试失败,比如:我需要传递一个字符串,其中包含了一个GUID+UserName,这里GUID可能我们无法模拟,所以,测试参数时,只需要测试UserName就可以。
方法代码如下:
1
2
3
4
5
6
|
public
bool
Valid_Paramter( string
userName, string
passWord)
{
Log.Write(Guid.NewGuid() + userName);
return
userName == "admin"
&& passWord == "123456" ;
}
|
测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[Test] public
void
Valid_Paramter()
{
MockRepository mock = new
MockRepository();
var
log = mock.DynamicMock<ILog>();
using
(mock.Record())
{
log.Write( "admin" );
LastCall.Constraints(Rhino.Mocks.Constraints.Text.Contains( "admin" ));
}
//这里代码如果不明白,可以看上一节,里面有说明。
MyLogin login = new
MyLogin();
login.Log = log;
var
valid = login.Valid_Paramter( "admin" , "123456" );
Assert.AreEqual(valid, true );
mock.VerifyAll();
}
|
这里只是测试模拟对象的参数传递。
4.测试数据库访问代码,这里提供的方法是,执行完测试代码后,对数据库进行回滚。有两种实现方式,一种是使用Rollback特性,一种是使用TransactionScope类。其实,Rollback也使用TransactionScope类。
1) Rollback特性
开始我以为Rollback是NUnit框架自带的一个特性,结果不是,需要自己编写代码,这里需要继承ITestAction接口,该接口包含BeforeTest方法和AfterTest方法。Rollback特性的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
RollbackAttribute : Attribute, ITestAction
{
private
TransactionScope transaction;
public
void
BeforeTest(TestDetails testDetails)
{
transaction = new
TransactionScope();
}
public
void
AfterTest(TestDetails testDetails)
{
transaction.Dispose();
}
public
ActionTargets Targets
{
get
{ return
ActionTargets.Test; }
}
}
|
下面是被测试的代码,这里我用了EF框架,代码非常简单,向TitleInfo表中,写入数据,该表中只有一个字段,就是title字段。如果写入成功,则返回值大于0.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
int
Insert( string
title)
{
testEntities db = new
testEntities();
var
titleInfo = new
TitleInfo()
{
Title = title
};
db.TitleInfo.Add(titleInfo);
return
db.SaveChanges();
}
|
使用Rollback特性进行测试。
1
2
3
4
5
6
7
8
9
10
|
[Test] [Rollback] public
void
Test_Insert()
{ DBLibrary db = new
DBLibrary();
var
count = db.Insert( "admin" );
Assert.True(count > 0);
} |
非常简单,加入Rollback特性即可,这里不会向数据库写入记录,但是测试会成功。
2) 使用TransactionScope类测试数据访问层,该类可以实现数据库的事务操作,执行测试代码后,会对数据库的事务进行回滚,达到测试的目的,而不需要修改数据库的数据。测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
[TestFixture] class
DBLibraryTest
{ TransactionScope socp;
[SetUp]
public
void
Init()
{
socp = new
TransactionScope();
}
[TearDown]
public
void
Close()
{
socp.Dispose();
}
[Test]
public
void
Test_Insert()
{
DBLibrary db = new
DBLibrary();
var
count = db.Insert( "admin" );
Assert.True(count > 0);
}
} |
这里加入了TransactionScope类,在Test_Insert方法执行前,会先执行Init方法,执行完Test_Insert方法后,执行Close方法,对事务进行回滚。其实Rollback只是对TransactionScope进行了封装,通过NUnit框架,很方便的实现了数据库事务的回滚。
总结:这里只是总结了常用的测试方法,如果大家还有其它的常见方法,可以分享一下,大家共同学习。