2010-02-22 3 views
0

Я пытаюсь написать некоторые интеграционные тесты для некоторых хранимых процедур и функций SQL-сервера. Я хотел бы иметь базу данных, в которой есть известный набор тестовых данных, а затем переносить каждый тест в транзакцию, откатывая ее по завершении, чтобы тесты были независимыми.Откат вложенных транзакций при модульном тестировании хранимых процедур

Хранимые процедуры/функции делают что-либо из довольно простых запросов присоединения, к сложной фильтрации со многими уровнями соединений, к вставке данных в несколько таблиц.

Существует несколько хранимых процедур, которые фактически используют транзакции - и поэтому их труднее проверить. Я покажу пример общего кода, который выполняется, но имейте в виду, что это обычно будет в двух разных точках (тестовая настройка/срыв и фактическая хранимая процедура). Для этого примера я также использую очень простую временную таблицу:

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», я бы ожидать «АААА» и «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», мы до сих пор получить «КПКГ» в table - он больше не откатывается, потому что транзакция не активна.

Есть ли способ обойти это? Есть ли лучшая стратегия для такого типа тестирования?

Примечание: Я не уверен, что когда-либо когда-либо делаются коды после отката или нет (часть «cccc»), но если это когда-либо произойдет, то преднамеренно или случайно, ломаются странными способами, поскольку неожиданные данные могут быть оставлены из другого теста.


Несколько похож на Nested stored procedures containing TRY CATCH ROLLBACK pattern?, но нет реального решения проблемы, поставленной здесь.

ответ

3

Отказы в коде не гнездятся. они откидывают все обратно к первой BEGIN TRANSACTION.

для каждой операции BEGIN TRANSACTION, @@ trancount увеличивается на один, однако любые ROLLBACK устанавливают @@ trancount обратно на ноль.

Если вы хотите откат части транзакции, вам нужно использовать точки сохранения TRANSACTION. вы можете посмотреть их в BOL, для получения дополнительной информации, чем я могу здесь.

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

+0

Одна из проблем заключается в том, что требуется, чтобы внутреннее исключение было изменено, то есть внутри проверяемого кода (хранимая процедура). Если кто-то не использует этот шаблон при написании транзакции в базе кода, он все равно может разбить тесты непредсказуемыми способами. Было бы лучше, если бы внешняя транзакция (часть тестовой среды) могла быть изменена, чтобы заставить ее работать для «обычной» транзакции, написанной в тестируемом коде. Спасибо, хотя, в противном случае это хорошая вещь, чтобы знать. – gregmac

+1

@gregmac, TSQL - это то, что есть. Я правильно объяснил, почему у вашего кода есть проблемы и какие у вас есть варианты. –

1

Я хотел бы иметь базу данных, которая имеет известный набор тестовых данных в нем, и затем обернуть каждый тест в транзакции, качению его обратно, когда полный, так что тесты фактически независимы.

Нельзя. Во-первых, вы фактически не будете тестировать функциональность, потому что в реальном мире процедуры будут совершены.Во-вторых, и это гораздо важнее, вы получите ложные сбои в газилионе и должны использовать обходные пути для чтения грязных данных, потому что вы на самом деле не совершаете, и вы не можете выполнить правильную проверку.

Вместо этого есть резервная копия базы данных с хорошо известным набором, а затем быстро восстановить ее перед тестом. Групповые тесты в наборах, которые могут запускаться при восстановлении новой базы данных, не затрагивая друг друга, поэтому вы уменьшаете количество необходимых восстановлений.

Вы также можете использовать моментальные снимки базы данных, выполнить моментальный снимок запуска набора, затем восстановить базу данных из моментального снимка перед каждым тестом, см. How to: Revert a Database to a Database Snapshot (Transact-SQL).

Или объедините два метода: установка набора (т. Е. Метод unit test @class) восстанавливает базу данных из файла .bak и создает моментальный снимок, затем каждый тест восстанавливает базу данных из моментального снимка.

+0

Я должен был уточнить настройку: фактические утверждения теста будут сделаны между окончанием теста и до разрыва - так что в этот момент данные будут зафиксированы. – gregmac

+0

Данные не могут быть зафиксированы, а затем отброшены назад. В этот момент данные не совершаются. –

0

У меня были подобные проблемы с такой настройкой, и я решил, что это должен был создать сценарий «SetupTest» и скрипт «ClearTest» для запуска до и после выполнения теста. Если вы не говорите об огромном объеме данных здесь, - что сделало бы выполнение теста слишком медленным, это должно работать хорошо и сделать тесты повторяемыми, потому что вы знаете, что каждый раз, когда вы запускаете этот набор тестов, у вас будет правильный данные, ожидающие исполнения.

Смежные вопросы