2010-02-22 73 views
0

我正在尝试为某些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

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

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

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

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

+0

这样做的一个问题是它需要修改内部异常 - 也就是被测代码(存储过程)中的一个。如果有人在代码库中编写事务时不使用这种模式,它仍然可能以不可预知的方式破坏测试。如果可以修改外部事务(测试框架的一部分)以使其适用于在正在测试的代码中编写的“常规”事务,那将会更好。不过谢谢,否则这是一件很好的事情要知道。 – gregmac 2010-02-22 22:40:53

+1

@gregmac,TSQL就是这样。我正确地解释了为什么你的代码有问题,以及你有什么选择。 – 2010-02-23 12:46:44

1

我想有一个具有 一组已知的测试数据中有一个数据库,然后 包装每个测试在一个事务中, 回滚完成时,使 测试是有效的独立。

不要。首先,你不会真正测试这个功能,因为在现实世界中,这些过程将会提交。其次,这是非常重要的,你会得到一个错误的失败,并且需要实现解决方法来读取脏数据,因为你实际上没有提交,并且你不能做任何适当的验证。

取而代之的是具有众所周知的数据库备份,然后在测试之前快速恢复它。将测试分组到各个套件中,这些套件可以在全新数据库恢复时运行,而不会相互影响,因此可以减少所需的恢复次数。

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

或者结合这两种方法:suite setup(即unit test @class method)从.bak文件恢复数据库并创建一个快照,然后每个测试都从快照中恢复数据库。

+0

我应该更清楚地了解这个设置:实际的测试断言将在测试结束和拆解之前完成 - 因此在这一点上,数据将被提交。 – gregmac 2010-02-22 19:48:52

+0

数据无法提交并在以后回滚。那时数据没有被提交。 – 2010-02-22 23:10:08

0

我有类似的问题与那种设置和我承担这是创建一个“SetupTest”脚本和“ClearTest”脚本运行前后执行测试。除非你在这里谈论大量的数据 - 这会使测试执行速度太慢,这应该很好,并使测试可重复,因为你知道每次运行测试套件时,你都会得到正确的等待执行的数据。