我最近正在调用一个在代码中包含 rasierror 的过程。raiserror 在 try catch 块中。在 raiserror 之后,BEGIN TRAN 也在同一个 try catch 块中。Catch 块旨在在事务中发生错误时回滚事务。这样做的方法是检查@@TRANCOUNT 是否大于 0 我知道它已经开始了一个事务并且需要回滚。使用 tSQLt 进行测试时,@@TRANCOUNT 始终 >0,因此如果它遇到 CATCH 块,则执行 ROLLBACK 并且 tSQLt 失败(因为 tSQLt 正在事务中运行)。当我遇到错误并且运行 CATCH 块时,tSQLt 总是无法通过测试。我无法测试 raiserror 的正确处理。您将如何创建一个可能回滚事务的测试用例?
4 回答
正如您所提到的,tSQLt 在其自己的事务中运行每个测试。要跟踪正在发生的事情,依赖于在测试完成时仍处于打开状态的同一事务。SQL Server 不支持嵌套事务,因此您的过程会回滚所有内容,包括框架为当前测试存储的状态信息。那时 tSQLt 只能假设发生了非常糟糕的事情。因此,它将测试标记为错误。
SQL Server 本身不鼓励在过程中进行回滚,如果在打开的事务中调用该过程,则会引发错误。有关处理这种情况的方法和一些其他信息,请查看我的博客文章,了解如何在程序中回滚。
当我刚刚阅读 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'
对以上两个答案+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)
最好BEGIN TRY
在BEGIN TRANSACTION
. 当我遇到类似问题时,我这样做了。这更合乎逻辑,因为在CATCH
块中我检查了IF @@TRANCOUNT > 0 ROLLBACK
. 如果之前引发了另一个错误,则不需要检查此条件BEGIN TRANSACTION
。在这种情况下,您可以测试您的RAISERROR
功能。