使用事务自动回滚来实现单元测试

我们没有使用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中就已经有了,可用于分布式事务。用这种方法来做数据的自动回滚也有一些不足:

  1. 数据库要支持事务。
  2. 内部数据库操作的逻辑里没有事务的实现。

很庆幸的是我们的项目正好都满足上面的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 
  

posted @ 2012-03-14 21:43  木子博客  阅读(2369)  评论(4编辑  收藏  举报