Как откатить транзакцию при тестировании с использованием tSQLt - PullRequest
5 голосов
/ 23 января 2012

Недавно я вызывал процедуру, которая содержала ошибку в коде.Рейзеррор был в блоке попытки поймать.Также BEGIN TRAN находился в том же блоке try catch после raiserror.Блок Catch предназначен для ROLLBACK транзакции, если в транзакции произошла ошибка.Для этого нужно проверить @@ TRANCOUNT, если оно больше 0. Я знаю, что он запустил транзакцию и нуждается в ROLLBACK.При тестировании с tSQLt @@ TRANCOUNT всегда> 0, поэтому, если он когда-либо попадет в блок CATCH, выполняется ROLLBACK и tSQLt завершается ошибкой (потому что tSQLt выполняется в транзакции).Когда я выдаю сообщение об ошибке и запускается блок CATCH, tSQLt всегда не проходит тест.У меня нет возможности проверить правильность обработки raiserror.Как бы вы создали тестовый сценарий, который потенциально может откатить транзакцию?

Ответы [ 4 ]

8 голосов
/ 24 января 2012

Как вы упомянули, tSQLt выполняет каждый тест в своей собственной транзакции. Чтобы отслеживать, что происходит, полагается, что та же самая транзакция будет все еще открыта, когда тест завершится. SQL Server не поддерживает вложенные транзакции, поэтому ваша процедура откатывает все, включая информацию о состоянии, хранимую для текущего теста. В этот момент tSQLt может только предполагать, что произошло что-то действительно плохое. Поэтому он помечает тест как ошибочный.

Сам SQL Server препятствует откату внутри процедуры, выдавая ошибку, если эта процедура была вызвана в открытой транзакции. Чтобы узнать, как справиться с этой ситуацией и получить дополнительную информацию, просмотрите мой пост в блоге о , как выполнить откат в процедурах .

.
2 голосов
/ 01 марта 2015

Поскольку я только что прочитал о 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'
0 голосов
/ 18 сентября 2014

+ 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

0 голосов
/ 19 марта 2014

Лучше использовать блок BEGIN TRY после BEGIN TRANSACTION. Я сделал это, когда у меня была похожая проблема. Это более логично, потому что в блоке CATCH я проверил IF @@TRANCOUNT > 0 ROLLBACK. Это условие не нужно проверять, если до BEGIN TRANSACTION возникла другая ошибка. И в этом случае вы можете проверить свою RAISERROR функциональность.

...