代码之家  ›  专栏  ›  技术社区  ›  gregmac

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

  •  0
  • gregmac  · 技术社区  · 14 年前

    我正在尝试为一些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 回复  |  直到 14 年前
        1
  •  3
  •   KM.    14 年前

    代码中的回滚不会嵌套。它们将所有内容回滚到第一个开始事务。

    对于每个begin事务,@@trancount都会增加1,但是,任何回滚都会将@@trancount设置回零。

    如果要回滚事务的一部分,则需要使用事务保存点。您可以在BOL中查找它们,以获取比我在此处输入的信息更多的信息。

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

        2
  •  1
  •   Remus Rusanu    14 年前

    我想要一个数据库 其中的一组已知测试数据,以及 然后在事务中包装每个测试, 完成后将其回滚,以便 测试实际上是独立的。

    不要这样做。首先,您不会实际测试功能,因为在现实世界中,过程将提交。第二,这一点更为重要,您将得到gazilion错误的失败,并且需要实现解决方法来读取脏数据,因为您实际上没有提交,并且您无法进行任何适当的验证。

    相反,使用已知集进行数据库备份,然后在测试之前快速恢复它。将测试分组到可以在新的数据库还原上运行而不会相互影响的套件中,这样可以减少所需的还原数量。

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

    或者结合使用这两种方法:suite安装(即unit test@class method)从.bak文件还原数据库并创建快照,然后每个测试从快照还原数据库。

        3
  •  0
  •   Wagner Silveira    14 年前

    我对这种设置也有类似的问题,我的任务是创建一个“setup test”脚本和一个“cleartest”脚本,在测试执行之前和之后运行。除非您在这里谈论的是大量的数据——这会使测试执行太慢,否则这将很好地工作,并使测试可重复,因为您知道,每次运行该测试套件时,都会有等待执行的正确数据。