7

我最近正在调用一个在代码中包含 rasierror 的过程。raiserror 在 try catch 块中。在 raiserror 之后,BEGIN TRAN 也在同一个 try catch 块中。Catch 块旨在在事务中发生错误时回滚事务。这样做的方法是检查@@TRANCOUNT 是否大于 0 我知道它已经开始了一个事务并且需要回滚。使用 tSQLt 进行测试时,@@TRANCOUNT 始终 >0,因此如果它遇到 CATCH 块,则执行 ROLLBACK 并且 tSQLt 失败(因为 tSQLt 正在事务中运行)。当我遇到错误并且运行 CATCH 块时,tSQLt 总是无法通过测试。我无法测试 raiserror 的正确处理。您将如何创建一个可能回滚事务的测试用例?

4

4 回答 4

8

正如您所提到的,tSQLt 在其自己的事务中运行每个测试。要跟踪正在发生的事情,依赖于在测试完成时仍处于打开状态的同一事务。SQL Server 不支持嵌套事务,因此您的过程会回滚所有内容,包括框架为当前测试存储的状态信息。那时 tSQLt 只能假设发生了非常糟糕的事情。因此,它将测试标记为错误。

SQL Server 本身不鼓励在过程中进行回滚,如果在打开的事务中调用该过程,则会引发错误。有关处理这种情况的方法和一些其他信息,请查看我的博客文章,了解如何在程序中回滚

于 2012-01-23T20:08:20.547 回答
3

当我刚刚阅读 tSQLt 时,这是我了解到每个测试都在事务中运行时首先想到的问题之一。由于我的一些存储过程确实启动事务,有些甚至使用嵌套事务,这可能变得具有挑战性。我学到的关于嵌套事务的知识,如果你应用以下规则,你可以让你的代码保持干净,避免不断的错误检查,并且仍然可以优雅地处理错误。

  • 打开交易时始终使用 TRY/CATCH 块
  • 除非引发错误,否则始终提交事务
  • 除非@@TRANCOUNT = 0,否则在引发错误时始终回滚事务
  • 除非您绝对确定在存储过程开始时没有打开事务,否则请始终重新引发错误。

记住这些规则是 proc 实现和测试代码的示例。

ALTER PROC testProc
    @IshouldFail BIT
AS
BEGIN TRY
    BEGIN TRAN

    IF @IshouldFail = 1
        RAISERROR('failure', 16, 1);

    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK;

    -- Do some exception handling

    -- You'll need to reraise the error to prevent exceptions about inconsistent 
    -- @@TRANCOUNT before / after execution of the stored proc.
    RAISERROR('failure', 16, 1);
END CATCH
GO


--EXEC tSQLt.NewTestClass 'tSQLt.experiments';
--GO
ALTER PROCEDURE [tSQLt.experiments].[test testProc nested transaction fails]
AS
BEGIN
    --Assemble
    DECLARE @CatchWasHit CHAR(1) = 'N';

    --Act
    BEGIN TRY
        EXEC dbo.testProc 1
    END TRY
    BEGIN CATCH 
        IF @@TRANCOUNT = 0
            BEGIN TRAN --reopen an transaction
        SET @CatchWasHit = 'Y';
    END CATCH

    --Assert
    EXEC tSQLt.AssertEqualsString @Expected = N'Y', @Actual = @CatchWasHit, @Message = N'Exception was expected'

END;
GO

ALTER PROCEDURE [tSQLt.experiments].[test testProc nested transaction succeeds]
AS
BEGIN
    --Act
    EXEC dbo.testProc 0

END;
GO

EXEC tSQLt.Run @TestName = N'tSQLt.experiments'
于 2015-02-28T23:19:20.580 回答
0

对以上两个答案+1。

但是,如果您不想使用 TRY .. CATCH,请尝试以下代码。线之间的部分-----代表测试,上面和下面代表 tSQLt,在它调用您的测试之前和之后。如您所见,在调用测试之前由 tSQLt 启动的事务仍然如预期的那样,无论是否发生错误。@@TRANSCOUNT 仍然是 1

您可以注释掉 RAISERROR 以在引发和不引发异常的情况下进行尝试。

SET NOCOUNT ON

BEGIN TRANSACTION  -- DONE BY tSQLt
PRINT 'Inside tSQLt before calling the test: @@TRANCOUNT = ' + CONVERT (VARCHAR, @@TRANCOUNT)

    ---------------------------------
    PRINT '  Start of test ---------------------------'

    SAVE TRANSACTION Savepoint
    PRINT '  Inside the test: @@TRANCOUNT = ' + CONVERT (VARCHAR, @@TRANCOUNT)


    BEGIN TRANSACTION -- PART OF THE TEST
    PRINT '    Transaction in the test: @@TRANCOUNT = ' + CONVERT (VARCHAR, @@TRANCOUNT)

        RAISERROR ('A very nice error', 16, 0)

        PRINT '  @@ERROR = ' + CONVERT(VARCHAR,@@ERROR)


    -- PART OF THE TEST - CLEAN-UP
    IF @@ERROR <> 0 ROLLBACK TRANSACTION Savepoint   -- Not all the way, just tothe save point
    ELSE COMMIT TRANSACTION

    PRINT '  About to finish the test: @@TRANCOUNT = ' + CONVERT (VARCHAR, @@TRANCOUNT)

    PRINT '  End of test ---------------------------'

    ---------------------------------

ROLLBACK TRANSACTION   -- DONE BY tSQLt
PRINT 'Inside tSQLt after finishing the test: @@TRANCOUNT = ' + CONVERT (VARCHAR, @@TRANCOUNT)

确认http://www.blackwasp.co.uk/SQLSavepoints.aspx上的信息和代码

于 2014-09-18T15:43:09.843 回答
0

最好BEGIN TRYBEGIN TRANSACTION. 当我遇到类似问题时,我这样做了。这更合乎逻辑,因为在CATCH块中我检查了IF @@TRANCOUNT > 0 ROLLBACK. 如果之前引发了另一个错误,则不需要检查此条件BEGIN TRANSACTION。在这种情况下,您可以测试您的RAISERROR功能。

于 2014-03-19T13:22:53.067 回答