我们没有使用TDD,所以单元测试最麻烦的就是准备测试的基础数据。我们现在是使用内存仓储来做单元测试,要为每个仓储都构造基础数据,非常麻烦。
前几天看xunit的源码,看到AutoRollbackAttribute这个特性,异常的兴奋 ^_^。怎么就忘了用事务的自动回滚呢?
我们看AutorollbackAttribute的具体实现:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public
class
AutoRollbackAttribute : BeforeAfterTestAttribute
{
IsolationLevel isolationLevel = IsolationLevel.Unspecified;
TransactionScope scope;
TransactionScopeOption scopeOption = TransactionScopeOption.Required;
long
timeoutInMS = -1;
/// <summary>
/// Gets or sets the isolation level of the transaction.
/// Default value is <see cref="IsolationLevel"/>.Unspecified.
/// </summary>
public
IsolationLevel IsolationLevel
{
get
{
return
isolationLevel; }
set
{ isolationLevel = value; }
}
/// <summary>
/// Gets or sets the scope option for the transaction.
/// Default value is <see cref="TransactionScopeOption"/>.Required.
/// </summary>
public
TransactionScopeOption ScopeOption
{
get
{
return
scopeOption; }
set
{ scopeOption = value; }
}
/// <summary>
/// Gets or sets the timeout of the transaction, in milliseconds.
/// By default, the transaction will not timeout.
/// </summary>
public
long
TimeoutInMS
{
get
{
return
timeoutInMS; }
set
{ timeoutInMS = value; }
}
/// <summary>
/// Rolls back the transaction.
/// </summary>
public
override
void
After(MethodInfo methodUnderTest)
{
scope.Dispose();
}
/// <summary>
/// Creates the transaction.
/// </summary>
public
override
void
Before(MethodInfo methodUnderTest)
{
TransactionOptions options =
new
TransactionOptions();
options.IsolationLevel = isolationLevel;
if
(timeoutInMS > 0)
options.Timeout =
new
TimeSpan(timeoutInMS * 10);
scope =
new
TransactionScope(scopeOption, options);
}
}
|
这里使用了.Net Framework自带的TransactionScope。TransactionScope在.NET 2.0中就已经有了,可用于分布式事务。用这种方法来做数据的自动回滚也有一些不足:
很庆幸的是我们的项目正好都满足上面的2点,唯一不足的就是mongodb不支持事务。所以就需要混合仓储实现了,事务数据库使用真实的仓储,mongodb使用内存仓储。
项目中是用VS自带的单元测试框架,也不想因为这一个特性而改用xunit,那就只能动手把这个迁移到VS的单元测试框架里了。
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
/// <summary>
/// 单元测试基类
/// </summary>
[TestClass]
public
class
BaseUnitTest
{
IsolationLevel _isolationLevel = IsolationLevel.Unspecified;
TransactionScopeOption _scopeOption = TransactionScopeOption.Required;
TransactionScope _transactionScope;
bool
_openAutoRollback =
true
;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="autoRollback">是否开启自动回滚,默认开启</param>
public
BaseUnitTest(
bool
autoRollback =
true
)
{
_openAutoRollback = autoRollback;
}
/// <summary>
/// 自动回滚事务初始化
/// </summary>
[TestInitialize]
public
void
AutoRollbackBefore()
{
if
(_openAutoRollback)
{
var options =
new
TransactionOptions();
options.IsolationLevel = _isolationLevel;
options.Timeout =
new
TimeSpan(0, 1, 0);
_transactionScope =
new
TransactionScope(_scopeOption, options);
}
}
/// <summary>
/// 自动回滚事务回滚并释放对象
/// </summary>
[TestCleanup]
public
void
AutoRollbackAfter()
{
if
(_openAutoRollback)
{
if
(_transactionScope ==
null
)
throw
new
InvalidOperationException(
"未初始化TransactionScope"
);
//回滚事务
_transactionScope.Dispose();
//释放事务对象
_transactionScope =
null
;
//移除所有的缓存
RemoveHttpRuntimeCache();
}
}
/// <summary>
/// 移除所有的HttpRuntime缓存
/// </summary>
[DebuggerStepThrough]
private
void
RemoveHttpRuntimeCache()
{
var cache = HttpRuntime.Cache.GetEnumerator();
var keys =
new
List<
string
>();
while
(cache.MoveNext())
{
keys.Add(cache.Key.ToString());
}
foreach
(var key
in
keys)
{
HttpRuntime.Cache.Remove(key);
}
}
/// <summary>
/// 设置不自动回滚事务
/// </summary>
protected
void
SetAutoRollbackIsUnavailabled()
{
_openAutoRollback =
false
;
}
}
|
上面的RemoveHttpRuntimeCache是因为我们在项目中有使用HttpRuntime缓存,关系数据库中的数据回滚后会导致缓存和数据库不一致,所以一但有开启事务的自动回滚,也要相应的清空内存缓存。
方法很简单,跟大家分享一下,和TransactionScope相关的知识,不清楚的同学可以看下MSDN关于“TransactionScope”的文档。
原文地址:http://blog.moozi.net/archives/use-the-transaction-rollback-to-unit-test.html