MSTest单元测试(VS 2012)与SQL Server,间歇性主键冲突



我正在处理一个非常奇怪的问题。最初,我认为这是清理测试数据的问题……但是在完全重构我的测试数据清理代码后,仍然看到完全相同的行为……我很困惑。

我在各种类中有245个单元测试方法。每个类都有自己唯一的测试数据,我初始化这些对象,然后在每个测试方法中通常将这些数据插入数据库,然后用测试进行操作。每个测试类都有一个ClassCleanup方法,它从数据库中清除所有的测试数据,并且ClassCleanup也在TestInitialize上运行,以确保在任何其他测试方法运行之前清除所有内容。

当我使用VS 2012测试资源管理器"运行所有"时,有22个测试失败。由于违反了主键约束,它们都失败了。这意味着,当它们为这些测试初始化数据时,没有从该类中的先前测试方法中清除数据。如果我重新运行所有测试,每次都会得到相同的测试失败。这是可重复的。不管我做多少次测试。这27个测试因违反主键而失败。

然而,奇怪的是,如果我只重新运行那些失败的测试,只有9个测试失败。这也是可重复的,这意味着,无论我只运行这27个先前失败的测试多少次,14个将失败,其余的将通过。这将继续,因为我只运行失败的测试,直到达到没有测试失败的程度。还应该注意的是,如果我单独运行每个测试类,则一切都通过了。

我知道这看起来像什么。

"你显然没有清理你的测试数据。"如果是这样的话,那么我应该看到相同的测试在每次运行中都失败,无论如何。这27个测试应该每次运行都失败,而不仅仅是在我运行其他所有测试时失败。

"类之间不能有唯一的主键,否则事情就不会被清理。"见上图。即使我在我的类中有重复的主键(我没有这样做,因为我亲自检查了各种类中测试数据的主键的唯一性),因为这些测试不是并发地在单独的线程上运行的(这已经通过记录ThreadId进行了验证),任何给定测试的清理代码都将清除重复的数据,无论如何。

"您一定没有使用连接池。"不,实际上我是。我已经使用SQL Profiler验证了请求肯定是池化的。而且,因为这些测试不是并行运行的,所以只有一个连接线程。

"你不应该使用连接池。"好吧,是的,我应该,因为底层代码库支持各种web项目,但为了争论的缘故,我尝试运行所有的测试与连接池禁用(使用池=false在连接字符串),我得到完全相同的结果。无论如何,行为没有改变。

"一定是你当地的环境出了问题。"我在其他同事的开发箱(顺便使用SQL 2012)上运行这些测试也得到了相同的结果。这不是我的环境所特有的,甚至不是我的SQL Server版本所特有的。

"您应该尝试从命令行运行mstest。"已经做过了。同样的结果。

如果有人遇到这样的事情,请让我知道。我知道我一定遗漏了一些简单的东西,因为这类问题通常都是这种情况,但我已经尽可能多地涵盖了这些基础,以试图解决这个问题。

以下是基于以下假设:您的数据库处于完全恢复模式,并且您在测试期间没有执行任何恢复或其他欺骗操作(例如分离/重新连接数据库等)。

这是一种相当乏味的调查问题的方法,但保证提供解决问题所需的数据。

  1. 对数据库进行完全备份在启动测试套件之前执行此操作。我们要恢复数据库,所以也要确保你有足够的磁盘空间来保存2-3个数据库文件的副本。

  2. 创建Sql Profiler跟踪对于事件,选择RPC Starting/completed、Sql Batch Starting/completed、Sql Statement Starting/completed、SP Statement Starting/completed、TM:* completed、SQLTransaction、DTCTransaction和用户错误消息。捕获所有列。

  3. 重现问题运行产生故障的最小测试次数。让测试完成,以便捕获所有清理代码,然后停止分析器跟踪。

  4. 进行事务日志备份我们可能会在以后的时间点恢复中需要它

  5. 在trace中定位故障如果您得到主键故障,那么它应该很容易跟踪,只需查找用户错误消息。

  6. 请写下错误发生的确切时间
  7. 检查明显问题的跟踪从错误开始并向后工作,直到找到失败的测试开始。写下最后一次失败测试启动的确切时间。检查此范围内的所有sql。sql 就是您所期望的吗?行数正确吗?transactionId是否正确?(transactionId列对于事务中的以外的每个语句都应该是不同的,对于事务中的中的每个语句都应该是相同的)。如果您有不匹配的BEGIN TRAN/COMMIT TRAN/ROLLBACK TRAN, transactionId会让您知道。

  8. 将DB恢复到失败的测试设置之前的正确位置将其恢复到新数据库,以便我们可以比较原始数据库和副本。首先使用"restore DATABASE ...."恢复完整备份NORECOVERY"。然后使用"restore log .."恢复事务日志备份。

  9. 使用STOPAT, RECOVERY"并指定测试设置失败之前的时间。
  10. 验证数据库状态检查可能尚未清理的测试数据。一切正常吗?如果没有,可以将数据库再次恢复到以前的某个点。您需要在测试开始之前查找数据库处于良好的已知状态的时间点。

  11. 恢复到错误发生前的数据库如果您有空间,恢复到另一个新的数据库。检查导致PK违规的数据。如果再次运行有问题的语句,会出现错误吗?验证它是否发生。

    • 如果没有发生,您的问题可能是不匹配的事务处理。如果您之前错过了COMMIT,则可能有一个事务仍然打开。当使用STOPAT进行恢复时,任何未提交的事务都将回滚。这也解释了为什么单独运行测试是有效的,但是一起运行测试却失败了。
    • 如果确实发生了,那么向后工作直到找到问题。您可能需要多次恢复DB才能找出问题所在。您的进程将是还原数据库、检查跟踪、检查数据、恢复到不同点、检查跟踪、检查数据等。
  12. 如果在所有这些之后您仍然感到困惑,那么您可能想要研究使用数据库快照作为单元测试的一部分。基本上,创建db快照,设置并运行test,将teardown替换为将数据库恢复到快照。这将保证每次测试前后的数据库相同。

2012 Management Studio有一个改进的数据库恢复向导,使时间点恢复非常容易。祝你好运!

不知道为什么你的失败,但我有类似的东西,现在我把一个transactionscope在这样的设置:

public void SetUp()
{
 _transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
}

并在拆解时处理。这解决了我的数据库问题,使我不必编写手动清理代码。