使用事务自动回滚来实现单元测试
我们没有使用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