我正在针对数据库编写一些单元测试,我们正在使用事务来确保我们的测试数据最终被删除。
我遇到的问题是我正在测试的方法正在使用自己的TransactionScope对象,而且在访问数据库时它似乎是阻塞的。
这是我测试的基类:
BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });
然后在我正在测试的方法中,它确实:
using (TransactionScope scope = new TransactionScope())
第二次范围内的代码第一次触及数据库时,它会挂起。我能解决这个问题吗?
答案 0 :(得分:6)
如果您正在使用数据库,那么您没有进行单元测试,而您遇到的问题是真正的单元测试使用Mocks和Stubs的原因之一。
现在您正在进行的测试非常有价值,在某些情况下,我实际上会进行测试,而不是单元测试。我标记了这种早期集成测试(EIT)。这里的关键点是,在使用真实的东西而不是单元测试模拟时,我们会发现一类全新的错误。而关键在于Real Thing。一旦你伪造了具有人工交易范围等的环境,你就会失去EIT的大部分好处,因为你没有捕捉到微妙的交互错误,或者(如你的情况)引入人为问题。
我会找到一种方法来使用足够的测试数据快速填充数据库,并将其恢复到测试之外的状态。 “重置为已知状态”脚本对于这类测试非常有用。
答案 1 :(得分:5)
当您嵌套TransactionScope
实例时,您最终可能会使用分布式事务,而不是简单的本地事务。此行为在使用的数据库之间会有所不同。例如,SQLServer 2008不会升级到DTX,除非实际涉及多个数据库。另一方面,Oracle将始终升级到分布式事务,因为它不支持单个本地事务的共享连接。
根据您正在使用的数据库和TransactionScopeOption
,您可能会遇到死锁。这是因为DTX通常需要表锁以确保它们可以原子方式提交。例如,在Oracle中,如果在完成之前启动DTX并崩溃或丢失连接,则最终可能会出现“疑难分布式事务”。此“In Doubt”事务可能会锁定一个或多个表,阻止其他会话修改它们,直到DBA对挂起的事务ID执行ROLLBACK FORCE
命令。某些数据库(如SQLServer)尝试检测此类死锁并终止其中一个违规事务......但这可以保证发生。
我建议你选择以下两个选项之一:
您可能还想查看数据库的挂起事务视图(在Oracle中称为PENDING_TRANS$ ...在SQLServer中有XACT_STATE()函数)。
答案 2 :(得分:3)
您必须提交基本事务才能取消阻止您的测试方法,我认为这不是您想要的排序行为。您需要让测试方法的事务加入基类中创建的外部“环境”(伞形/父/基/外)事务,而不是尝试创建自己的事务。
来自MSDN CommittableTransactionClass备注(强调我的):
建议您创建 隐式事务使用 TransactionScope类,这样的 环境事务上下文是 自动为您管理。 您 还应该使用TransactionScope 和DependentTransaction类 需要使用的应用程序 多个相同的交易 函数调用或多线程 电话。有关这方面的更多信息 模型,请参阅Implementing An Implicit Transaction Using Transaction Scope主题。
创建CommittableTransaction 不自动设置环境 交易,这是交易 你的代码执行。你可以得到或 通过调用设置环境事务 静态的Current属性 全局Transaction对象。更多 有关环境交易的信息, 请参阅“管理交易流程” 使用TransactionScopeOption“部分 实施隐含的 使用交易范围的交易 话题。如果是环境交易 没有设置,资源上的任何操作 经理不是其中的一部分 交易。你需要明确 设置并重置环境事务, 确保资源经理 在正确的交易下运作 上下文。
在提交CommittableTransaction之前,与交易有关的所有资源仍然处于锁定状态。
正如djna指出的那样,使用事务来回滚测试期间所做的更改是相当滥用的。您测试应该是一个好公民,撤消和更改它使自己成为finally子句中的数据库,以便其后可能运行的其他测试永远不会受到影响。如果你有很多测试表现不佳,那么你现在可能不会选择这条路线了。在这种情况下,更改基数以使用范围设置为RequiresNew
的隐式事务,并在测试方法中使用Required
。