2008-08-22 6 views
32

Это последнее летнее время я разрабатывал базовое приложение ASP.NET/SQL Server CRUD, а модульное тестирование было одним из требований. Я столкнулся с некоторыми проблемами, когда пытался проверить базу данных. В моем понимании, юнит-тесты должны быть:Базы данных для тестирования модулей

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

Эти требования, как представляется, противоречат друг другие при разработке для базы данных. Например, я не могу проверить Insert(), не убедившись, что строки, которые будут вставлены, еще не установлены, поэтому мне нужно сначала вызвать Delete(). Но что, если их еще нет? Тогда мне нужно будет сначала вызвать функцию Exists().

Мое возможное решение включало очень большие функции настройки (yuck!) И пустой тестовый сценарий, который запускался первым и указывал бы, что установка работает без проблем. Это жертвует независимостью тестов, сохраняя при этом их безгражданство.

Другое решение, которое я нашел, - это обернуть вызовы функций в транзакции, которую можно легко откат, например Roy Osherove's XtUnit. Эта работа, но она включает в себя другую библиотеку, другую зависимость, и кажется, что она слишком тяжела для решения проблемы.

Итак, что было сделано сообществом SO, столкнувшись с этой ситуацией?


tgmdbm сказал:

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

Так что, если я прочитал это правильно, нет действительно никакого способа эффективно блок-тесте доступа к данным слоя. Или же «единичный тест» уровня доступа к данным включает тестирование, скажем, SQL/команд, генерируемых классами, независимо от фактического взаимодействия с базой данных?

ответ

25

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

Вы не типично единица проверить базу данных. Обычно вы используете базу данных в интеграции тестов.

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

11

DBunit

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

5

Обычное решение для внешних зависимостей в модульных тестах - использовать макетные объекты, то есть библиотеки, которые имитируют поведение реальных, с которыми вы тестируете. Это не всегда просто, и иногда требуется некоторая изобретательность, но есть несколько хороших (бесплатных) макетных библиотек для .Net, если вы не хотите «сворачивать свои собственные». Два сразу приходят на ум:

Rhino Mocks - это тот, который имеет довольно хорошую репутацию.

NMock - другой.

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

В базе данных mocks это означает «высмеивание» вашего собственного уровня доступа к БД с возвращаемыми объектами, которые составляют объекты таблицы, строки или набора данных для ваших модульных тестов.

Где я работаю, мы обычно делаем свои собственные макеты с нуля, но это не значит, что вам нужно.

1

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

4

Да, вы должны реорганизовать свой код, чтобы получить доступ к Хранилищам и службам, которые обращаются к базе данных, и затем вы можете имитировать или заглушить эти объекты, чтобы тестируемый объект никогда не касался базы данных. Это намного быстрее, чем сохранение состояния базы данных и ее сброс после каждого теста!

Я настоятельно рекомендую Moq как ваш издевательский каркас. Я использовал Rhino Mocks и NMock. Мок был настолько прост и решал все проблемы, которые у меня были с другими рамками.

0

Если вы используете LINQ to SQL в качестве ORM, вы можете сгенерировать базу данных «на лету» (при условии, что у вас достаточно доступа из учетной записи, используемой для модульного тестирования). См. http://www.aaron-powell.com/blog.aspx?id=1125

2

У меня был такой же вопрос, и вы пришли к тем же основным выводам, что и другие ответчики здесь: Не утруждайте себя тестированием фактического уровня связи db, но если вы хотите, чтобы модуль тестировал ваши функции модели (чтобы обеспечить правильное извлечение данных, правильное форматирование и т. д.), используйте какой-то фиктивный источник данных и установочные тесты для проверки извлекаемых данных.

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

Unit Test Patterns

2

Я объяснил технику, что я использую для этой самой ситуации here ,

Основная идея состоит в том, чтобы использовать каждый метод в вашем DAL - утверждать свои результаты - и когда каждый тест завершен, откат, чтобы ваша база данных была чистой (никаких данных об мусоре/тестах).

Единственная проблема, с которой вы, возможно, не найдете «замечательный», - это то, что я обычно выполняю весь тест CRUD (не чистый с точки зрения тестирования устройства), но этот интеграционный тест позволяет вам увидеть ваш код отображения CRUD + в действии. Таким образом, если он сломается, вы узнаете, прежде чем запускать приложение (сэкономит мне массу работы, когда я пытаюсь идти быстро)

1

Тестирование уровня данных и базы данных вместе оставляет мало сюрпризов для более поздних версий в проект. Но тестирование с базой данных имеет свои проблемы, главным из которых является то, что вы тестируете против состояния, разделяемого многими тестами. Если вы вставляете строку в базу данных в одном тесте, следующий тест также может увидеть эту строку.
Что вам нужно, это откат изменений, внесенных в базу данных.
Класс TransactionScope достаточно умен, чтобы обрабатывать очень сложные транзакции, , а также вложенные транзакции, в которых ваш код под тестовыми вызовами совершает собственную локальную транзакцию . Вот простой фрагмент кода, который показывает, как легко добавить откат способности тестов:

[TestFixture] 
    public class TrannsactionScopeTests 
    { 
     private TransactionScope trans = null; 

     [SetUp] 
     public void SetUp() 
     { 
      trans = new TransactionScope(TransactionScopeOption.Required); 
     } 

     [TearDown] 
     public void TearDown() 
     { 
      trans.Dispose(); 
     } 

     [Test] 
     public void TestServicedSameTransaction() 
     { 
      MySimpleClass c = new MySimpleClass(); 
      long id = c.InsertCategoryStandard("whatever"); 
      long id2 = c.InsertCategoryStandard("whatever"); 
      Console.WriteLine("Got id of " + id); 
      Console.WriteLine("Got id of " + id2); 
      Assert.AreNotEqual(id, id2); 
     } 
    } 
Смежные вопросы