单元测试存储过程时回滚嵌套事务

时间:2010-02-22 19:28:49

标签: sql-server unit-testing integration-testing

我正在尝试为某些SQL Server存储过程和函数编写一些集成测试。我希望有一个数据库,其中包含一组已知的测试数据,然后将每个测试包装在一个事务中,在完成时将其回滚,以便测试实际上是独立的。

存储过程/函数可以执行任何操作,从相当简单的连接查询到具有多层连接的复杂过滤,再到将数据插入到多个表中。

有一些实际使用事务的存储过程 - 因此这些很难测试。我将展示执行的整体代码的示例,但请记住,这通常会在两个不同的位置(测试设置/拆卸,以及实际的存储过程)。对于这个示例,我还使用了一个非常简单的临时表:

CREATE TABLE #test (
  val nvarchar(500)
)

示例:

-- for this example, just ensuring that the table is empty
delete from #test
go


-- begin of test setup code --
begin transaction 
go
-- end of test setup code --

    -- begin of code under test --
    insert into #test values('aaaa')

    begin transaction 
    go

        insert into #test values('bbbbb')

    rollback transaction 
    go

    insert into #test values('ccccc')

    -- Example select #1:
    select * from #test

    -- end of code under test --

-- begin of test teardown --
rollback transaction 
go
-- end of test teardown

-- checking that #temp is still empty, like it was before test  
-- Example select #2:
select * from #test

这里的问题是在“示例选择#1”中,我希望“aaaa”和“cccc”在表中,但实际上只有“cccc”在表中,因为SQL Server实际上回滚了所有交易(见http://abdulaleemkhan.blogspot.com/2006/07/nested-t-sql-transactions.html)。此外,第二次回滚会导致错误,尽管可以通过以下方式避免这种情况:

-- begin of test teardown --
if @@trancount > 0 
begin
    rollback transaction 
end
go
-- end of test teardown

它没有解决真正的问题:在“示例选择#2”中,我们仍然在表中获得“cccc” - 它不再被回滚,因为没有活动的事务。

有解决方法吗?这种类型的测试有更好的策略吗?

注意:我不确定代码库是否在回滚后是否执行了任何操作(插入'cccc'部分) - 但如果它曾经有意或无意地执行,则测试可能会中断以奇怪的方式,因为意外的数据可以从另一个测试遗留下来。


Nested stored procedures containing TRY CATCH ROLLBACK pattern?有些相似,但这里没有真正解决问题的方法。

3 个答案:

答案 0 :(得分:3)

代码中的回滚不会嵌套。他们将所有内容回滚到第一个BEGIN TRANSACTION。

对于每个BEGIN TRANSACTION,@@ trancount增加1,但是,任何ROLLBACK都将@@ trancount设置为零。

如果要回滚事务的一部分,则需要使用TRANSACTION保存点。你可以在BOL中查找它们,了解更多信息,我可以在这里输入。

http://msdn.microsoft.com/en-us/library/ms188378.aspx

答案 1 :(得分:1)

  

我想拥有一个拥有的数据库   一组已知的测试数据,和   然后在事务中包装每个测试,   完成后回滚它   测试实际上是独立的。

别。首先,您实际上不会测试功能,因为在现实世界中,程序将提交。其次,这更重要的是,你会得到一个虚假的失败,并且需要实现变通方法来读取脏数据,因为你实际上没有提交,并且你无法进行任何适当的验证。

而是使用众所周知的设置进行数据库备份,然后在测试之前快速恢复它。将测试分组到可以全部运行在新数据库还原上的套件,而不会相互影响,因此可以减少所需的还原次数。

您还可以使用数据库快照,在套件启动时拍摄快照,然后在每次测试之前从快照还原数据库,请参阅How to: Revert a Database to a Database Snapshot (Transact-SQL)

或者将两种方法结合起来:套件设置(即单元测试@class方法)从.bak文件恢复数据库并创建快照,然后每个测试从快照恢复数据库。

答案 2 :(得分:0)

我在这种设置方面遇到了类似的问题,我的看法是创建一个“SetupTest”脚本和一个“ClearTest”脚本,在测试执行之前和之后运行。除非你在这里谈论大量数据 - 这会使测试执行太慢,这应该可以正常工作并使测试可重复,因为你知道每次运行该测试套件时,你都会有正确的等待执行的数据。