модульное/интеграционное тестирование и восстановление баз данных

Занят с автоматическими тестами на C#, и нам нужно восстанавливать моментальный снимок БД после каждого теста. Проблема в том, что при выполнении нескольких тестов происходит сбой, потому что «состояние базы данных не может быть изменено, пока другие пользователи используют базу данных».

Мы используем SqlConnection.ClearAllPools(); перед восстановлением, но после четвертого теста он снова не восстанавливается для запуска, и кажется, что пулы перестают очищаться. (Почему четыре? См. редактирование 2)

Как я могу сделать это более надежным?

ИЗМЕНИТЬ

Может быть, я должен дать больше информации о системе. Это интеграционные тесты unit для сервиса. Тесты ссылаются на сервисную dll (без ссылки на сервис, мы напрямую обращаемся к методам сервиса). Итак, в тестах нет никакого SQL, кроме восстановления снимка БД после каждого тестового блока.

Это сервис, поэтому мы не совсем управляем соединениями. У нас есть центральная точка, где мы создаем объекты базы данных, из которых мы получаем наши DbCommands для выполнения хранимых процедур.

Используя sp_who2 в студии SQL, я наблюдаю следующее: существует одна сессия с БД для первых четырех тестовых блоков (где каждый блок разделен ClearAllPools() и восстановлением снимка), но по состоянию на пятый тестовый блок есть три сессии против этого. (Почему? Это может быть ключом к проблеме.) (Восстановление моментального снимка открывает дополнительное соединение с главной БД.) Все открытые соединения находятся в состоянии ожидания, ожидая команды, даже то, которое блокирует соединение для восстановления моментального снимка.

ИЗМЕНИТЬ 2

Почему пять? Я думал, что тестовые случаи будут выполняться случайным образом, но я ошибался. Я нашел виновного. Открывается более одного соединения, система использует только последнее, а остальные остаются зависшими и не будут очищены, пока вы не выйдете.


person Peet Brits    schedule 13.12.2011    source источник
comment
Что вы делаете для управления своими SQL-соединениями в тестируемом коде?   -  person Stealth Rabbi    schedule 13.12.2011
comment
Stealth Rabbi: Не уверен, что вы имеете в виду под управлением, но это услуга, так что, скорее всего, ничего. Я обновил свой вопрос с дополнительной информацией.   -  person Peet Brits    schedule 13.12.2011
comment
Я имею в виду, как вы открываете/закрываете свои соединения и транзакции. Кроме того, если вы используете реальную базу данных, это действительно интеграционные тесты.   -  person Stealth Rabbi    schedule 13.12.2011
comment
Мы просто звоним, например. db.ExecuteNonQuery(command, ...), и большинство отдельных вызовов не содержат транзакций. Это для каждого метода, а не глобально. (У нас есть структура для обработки большинства общих случаев, но она выглядит похожей)   -  person Peet Brits    schedule 14.12.2011


Ответы (3)


Перед восстановлением моментального снимка установите базу данных в однопользовательский режим:

   ALTER DATABASE <mydb> SET SINGLE_USER WITH ROLLBACK IMMEDIATE

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

   ALTER DATABASE <mydb> SET SINGLE_USER

Это хак, но в противном случае очень сложно заставить восстановление моментального снимка работать последовательно. (идиома: обжегся на молоке - дует на воду.)

person Filip De Vos    schedule 14.12.2011
comment
+1, я думаю, что это ближе к тому, чего хочет ОП, чем мое решение :) - person Stefan Paul Noack; 14.12.2011
comment
Если SqlConnection.ClearAllPools() не выйдет из спящего соединения, то я думаю, что его уничтожение - единственный способ. - person Peet Brits; 14.12.2011

Я использую другой подход. Я запускаю тесты в транзакции, которая будет удаляться (откатывать) в конце каждого теста. Таким образом, вам не нужно выбрасывать базу данных при каждом тестовом сеансе, потому что база данных всегда «чиста». В C# вы можете создать TransactionScope и удалить его после теста или (лучше), если вы используете xUnit.net, вы можете использовать атрибут AutoRollback.

person ema    schedule 13.12.2011
comment
С TransactionScope должен ли я по-прежнему устанавливать транзакцию для каждой команды sql? Это будет проблемой, потому что система довольно большая. - person Peet Brits; 13.12.2011
comment
Неа. У вас есть различные варианты, лучше всего создать транзакцию в настройках и удалить ее в TearDown. Если у вас есть базовый класс для ваших тестов, вы можете использовать его для управления жизненным циклом транзакции. - person ema; 13.12.2011
comment
ПРОТИВ: TransactionScope требует от меня запуска службы координатора распределенных транзакций, и я теряю возможность выполнять код SQL в студии во время отладки (он не фиксируется из-за транзакции). - person Peet Brits; 13.12.2011
comment
+1 за рабочий ответ, но я держу его открытым некоторое время на случай, если кто-то еще узнает о проблеме с незакрытием пулов. - person Peet Brits; 14.12.2011
comment
Ну... а что, если вы хотите проверить, как ваше приложение обрабатывает транзакции? - person Stefan Paul Noack; 14.12.2011
comment
Транзакции и тестирование были скорее запоздалыми размышлениями. Не за мной последнее слово по этому поводу. Особенности для сроков имеют приоритет. Мне все еще нужно убедить их, что нам нужны тесты, чтобы увидеть, что происходит, когда что-то ломается. - person Peet Brits; 14.12.2011
comment
@PeetBrits DTC обычно требуется для доступа к нескольким базам данных или для нескольких подключений к одной и той же базе данных. Можно изменить код, чтобы всегда использовать одно и то же соединение, и все будет в порядке. - person Christian Droulers; 19.11.2014

Мы делаем то же самое. Перед каждым тестом восстанавливайте БД из резервной копии:

ALTER DATABASE <dbname> SET OFFLINE WITH ROLLBACK IMMEDIATE
DROP DATABASE <dbname>
RESTORE DATABASE <dbname> FROM DISK= ...

В случае, если база данных еще не существует, мы также проверяем это с помощью

IF DB_ID (N'<dbname>') IS NOT NULL

Надеюсь это поможет

person Stefan Paul Noack    schedule 14.12.2011