TransactionScope 导致阻塞?
我正在针对数据库编写一些单元测试,并且我们正在使用事务来确保我们的测试数据最终被删除。
我遇到了一个问题,我正在测试的方法正在使用它们自己的 TransactionScope 对象,并且在访问数据库时似乎会阻塞。
这是在我的测试的基类中:
BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });
然后在我正在测试的方法中,它会:
using (TransactionScope scope = new TransactionScope())
第二个范围内的代码第一次接触数据库时,它会挂起。我有办法解决这个问题吗?
I'm writing some Unit tests against a database, and we're using transactions to make sure that our test data gets removed at the end.
I'm running into a problem where methods that I'm testing are using their own TransactionScope objects, and it seems to be blocking when hitting the database.
This is inside my test's base class:
BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });
and then inside the method I'm testing, it does:
using (TransactionScope scope = new TransactionScope())
The first time that the code inside the 2nd scope their touches the database, it hangs. Do I have any way around this problem?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您使用的是数据库,那么您就没有进行单元测试,而您遇到的问题是真正的单元测试使用模拟和存根的原因之一。
现在你正在做的测试非常有价值,在某些情况下我实际上会做它们而不是单元测试。我将此标记为早期集成测试(EIT)。这里的关键点是,我们在使用真实的东西而不是单元测试模拟时发现了一类全新的错误。这里的关键是真实的东西。一旦你用人为的交易范围等伪造了环境,你就会失去 EIT 的大部分好处,因为你没有发现微妙的交互错误,或者(如你的情况)引入了人为的问题。
我会找到一种方法,用足够的测试数据快速填充数据库,并将其恢复到测试之外的状态。 “重置为已知状态”脚本对于此类测试非常有帮助。
If you are using a Database then you are not doing Unit testing, and the problems you are experiencing are one of the reason why true Unit testing uses Mocks and Stubs.
Now the tests you are doing are very valuable, and in some cases I would actually do them instead of Unit Testing. I label this Early Integration Testing (EIT). The key point here is that we find a whole new class of bugs when working with the real thing not the Unit Test mocks. And the key here is Real Thing. As soon as you fake up the environment with artifical transaction scopes etc. you are losing much of the benefit of EIT because you don't catch subtle interaction errors, or (as in your case) introduce artificial problems.
I would find a way to quickly populate the database with sufficient test data, and restore it to that state outside the test. A "reset to known state" script is very helpful for these kind of tests.
当您嵌套
TransactionScope
实例时,您最终会得到一个分布式事务,而不是一个简单的本地事务。这种行为在所使用的数据库之间有所不同。例如,SQLServer 2008 不会升级到 DTX,除非实际涉及多个数据库。另一方面,Oracle 将始终升级为分布式事务,因为它不能不支持单个本地事务的共享连接。根据您使用的数据库和
TransactionScopeOption
,您可能会遇到死锁。发生这种情况是因为 DTX 通常需要表锁来确保它们可以原子提交。例如,在 Oracle 中,如果启动 DTX 并在完成之前崩溃或丢失连接,则可能会出现“可疑分布式事务”。这一“不确定”事务可能会锁定一个或多个表,以防止其他会话修改它们,直到 DBA 对挂起的事务 ID 执行 ROLLBACK FORCE 命令。某些数据库(如 SQLServer)尝试检测此类死锁并终止有问题的事务之一……但这肯定会发生。我建议您选择以下两个选项之一:
您可能还想查看数据库的待处理事务视图(在 Oracle 中,它称为 PENDING_TRANS$ ...在 SQLServer 中有 XACT_STATE() 函数)。
When you nest
TransactionScope
instances you can end up with a distributed transaction, rather than a simple local transaction. This behavior varies somewhat between database being used. SQLServer 2008, for instance doesn't escalate to a DTX unless multiple databases are actually involved. Oracle, on the other hand, will always escalate to a distributed transaction since it can't does not support sharing connections for a single local transaction.Depending on which database and what
TransactionScopeOption
you are using, you may end up with a deadlock. This occurs because DTXs often require table locks to ensure that they can be committed atomically. In Oracle, for example, if you start a DTX and crash or lose your connection before you complete it, you can end up with an "In Doubt Distributed Transaction". This "In Doubt" transaction may lock one or more tables preventing other session from modifying them until a DBA performs aROLLBACK FORCE
command on the pending transaction ID. Some databases (like SQLServer) attempt to detect such deadlocks and terminate one of the offending transactions ... but this is guaranteed to happen.I would suggest one of two options for you:
You may also want to look into your database's pending transaction view (in Oracle it's called PENDING_TRANS$ ... in SQLServer there's the XACT_STATE() function).
您必须提交基本事务才能解锁您的测试方法,我认为这不是您想要的排序行为。您需要让测试方法的事务加入在基类中创建的外部“环境”(伞/父/基/外部)事务,而不是尝试创建自己的事务。
来自 MSDN CommittableTransactionClass 备注(强调我的):
正如 djna 指出的那样,使用事务来回滚测试期间所做的更改是相当滥用的。您的测试应该是一个好公民,并在finally子句中撤消和更改它对数据库的影响,以便在它之后运行的其他测试永远不会受到影响。如果您有很多表现不佳的测试,那么您现在可能不会走这条路。在这种情况下,请将您的基础更改为使用范围设置为
RequiresNew
的隐式事务,并在您的测试方法中使用Required
。You'll have to commit your base transaction in order to unblock your test method, which I assume isn't the sort behavior you desire. You need to get your test method's transaction to join the outer "ambient" (umbrella/parent/base/outer) transaction created in your base class, rather than try to create its own.
From MSDN CommittableTransactionClass Remarks (emphasis mine):
As djna pointed out, using transactions to rollback changes made during testing is rather abusive. You test should be a good citizen and undo and changes it makes itself to the database in a finally clause so that other tests that may run after it never are affected. If you have lots of tests that aren't well behaved already, then you're probably not going to go this route now. In that case, change your base to use implicit transactions with a scope set to
RequiresNew
, and in your test method, useRequired
.