Данные базы данных, необходимые в интеграционных тестах; создан вызовами API или с использованием импортированных данных? - PullRequest
57 голосов
/ 27 января 2009

Этот вопрос более или менее независим от языка программирования. Однако, поскольку в эти дни я в основном разбираюсь в Java, я приведу свои примеры. Я также думаю о случае ООП, поэтому, если вы хотите протестировать метод, вам нужен экземпляр этого класса методов.

A основное правило для модульных тестов заключается в том, что они должны быть автономными, и этого можно добиться, изолировав класс от его зависимостей. Есть несколько способов сделать это, и это зависит от того, внедряете ли вы свои зависимости, используя IoC (в мире Java у нас есть Spring, EJB3 и другие фреймворки / платформы, которые предоставляют возможности внедрения) и / или если вы имитируете объекты (для Java у вас есть JMock и EasyMock ) для отделения тестируемого класса от его зависимостей.

Если нам нужно протестировать группы методов в разных классах * и увидеть, что они хорошо интегрируются, мы пишем интеграционные тесты . И вот мой вопрос!

  • По крайней мере в веб-приложениях состояние часто сохраняется в базе данных. Мы могли бы использовать те же инструменты, что и для модульных тестов, чтобы добиться независимости от базы данных. Но, по моему скромному мнению, я думаю, что бывают случаи, когда неиспользование базы данных для интеграционных тестов слишком насмешливо (но не стесняйтесь не соглашаться; вообще не пользуйтесь базой данных вообще, это также правильный ответ, поскольку делает вопрос неуместным ).
  • Когда вы используете базу данных для интеграционных тестов, как вы заполняете эту базу данных? Я вижу два подхода:
    • Сохраните содержимое базы данных для интеграционного теста и загрузите его перед началом теста. Если он хранится в виде дампа SQL, файла базы данных, XML или чего-либо еще, было бы интересно узнать.
    • Создание необходимых структур базы данных с помощью вызовов API. Эти вызовы, вероятно, разделены на несколько методов в тестовом коде, и каждый из этих методов может завершиться ошибкой. Это можно рассматривать как интеграционный тест, имеющий зависимости от других тестов.

Как вы убедитесь, что данные базы данных, необходимые для тестов, есть, когда они вам нужны? И почему вы выбрали метод, который вы выбрали?

Пожалуйста, предоставьте ответ с мотивом , так как именно в мотивации лежит интересная часть. Помните, что просто говорите "Это лучшая практика!" это не реальная мотивация, это просто повторение того, что вы прочитали или услышали от кого-то. Для этого случая, пожалуйста, объясните , почему это лучшая практика.

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

Ответы [ 8 ]

50 голосов
/ 12 февраля 2009

Я предпочитаю создавать тестовые данные с помощью вызовов API.

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

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

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

Если тестовые данные указаны в коде, у вас под рукой будут инструменты рефакторинга вашей IDE. Когда вы вносите изменения, которые влияют на схему базы данных, это, вероятно, также повлияет на вызовы API, поэтому в любом случае вам придется реорганизовать код с помощью API. Приблизительно с такими же усилиями вы можете также выполнить рефакторинг для создания тестовых данных, особенно если рефакторинг может быть автоматизирован (переименование, введение параметров и т. Д.). Но если тесты основаны на дампе базы данных, вам потребуется вручную выполнить рефакторинг дампа базы данных в дополнение к рефакторингу кода, использующего API.

Еще одна вещь, связанная с интеграционным тестированием базы данных, это тестирование того, что обновление с предыдущей схемы базы данных работает правильно. Для этого вы можете прочитать книгу Рефакторинг баз данных: Эволюционный дизайн баз данных или эту статью: http://martinfowler.com/articles/evodb.html

5 голосов
/ 16 февраля 2009

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

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

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

3 голосов
/ 16 февраля 2009

Почему эти два подхода определены как исключительно?

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

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

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

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

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

3 голосов
/ 13 февраля 2009

Похоже, ваш вопрос на самом деле два вопроса. Если вы используете исключить базу данных из вашего тестирования? Когда у вас есть база данных, то как вы должны генерировать данные в базе данных?

По возможности я предпочитаю использовать реальную базу данных. Часто запросы (написанные на SQL, HQL и т. Д.) В классах CRUD могут возвращать удивительные результаты, когда сталкиваются с реальной базой данных. Лучше избавиться от этих проблем на ранней стадии. Часто разработчики пишут очень тонкие модульные тесты для CRUD; тестирование только самых доброкачественных случаев. Использование реальной базы данных для тестирования может проверить все возможные случаи, о которых вы, возможно, даже не подозревали.

При этом могут быть и другие проблемы. После каждого теста вы хотите вернуть свою базу данных в известное состояние. В моей нынешней работе мы уничтожили базу данных, выполнив все операторы DROP, а затем полностью воссоздав все таблицы с нуля. Это очень медленно в Oracle, но может быть очень быстро, если вы используете базу данных в памяти, такую ​​как HSQLDB. Когда нам нужно устранить специфичные для Oracle проблемы, мы просто изменяем URL-адрес базы данных и свойства драйвера, а затем работаем с Oracle. Если у вас нет такой переносимости базы данных, то Oracle также имеет некоторую функцию снимка базы данных, которую можно использовать специально для этой цели; откат всей базы данных до какого-то предыдущего состояния. Я уверен, что есть другие базы данных.

В зависимости от того, какие данные будут в вашей базе данных, API или подход к загрузке могут работать лучше или хуже. Если у вас высокоструктурированные данные с множеством связей, API-интерфейсы сделают вашу жизнь проще, сделав отношения между вашими данными явными. Вам будет сложнее ошибиться при создании набора тестовых данных. Как уже упоминалось другими авторами, инструменты рефакторинга могут позаботиться о некоторых изменениях в структуре ваших данных автоматически. Часто я нахожу полезным думать о тестовых данных, сгенерированных API, как о создании сценария; когда пользователь / система выполнили шаги X, Y Z, а затем начнутся тесты. Эти состояния могут быть достигнуты, потому что вы можете написать программу, которая вызывает тот же API, который будет использовать ваш пользователь.

Загрузка данных становится гораздо важнее, когда вам нужны большие объемы данных, у вас мало связей между данными или есть согласованность в данных, которые невозможно выразить с помощью API или стандартных реляционных механизмов. На одной работе, которая работала в моей команде, писал приложение для отчетности для большой системы проверки сетевых пакетов. Объем данных был довольно большим для того времени. Чтобы вызвать полезное подмножество тестовых случаев, нам действительно нужны тестовые данные, генерируемые анализаторами. Таким образом, корреляции между информацией об одном протоколе будут коррелировать с информацией о другом протоколе. Было трудно уловить это в API.

Большинство баз данных имеют инструменты для импорта и экспорта текстовых файлов с разделителями таблиц. Но часто вам нужны только их подмножества; сделать использование дампов данных более сложным. На моей нынешней работе нам нужно взять несколько дампов реальных данных, которые генерируются программами Matlab и хранятся в базе данных. У нас есть инструмент, который может вывести подмножество данных базы данных, а затем сравнить их с «основополагающей правдой» для тестирования. Кажется, наши инструменты для извлечения постоянно модифицируются.

3 голосов
/ 12 февраля 2009

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

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

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

Очевидно, что эти тесты были намного медленнее , чем чистые модульные тесты.

Когда вы пытаетесь протестировать какой-то унаследованный код, где очень сложно написать модульные тесты для отдельных классов, этот подход может стоить усилий.

1 голос
/ 27 января 2009

Я делаю оба, в зависимости от того, что мне нужно проверить:

  • Я импортирую данные статического теста из сценариев SQL или дампов БД. Эти данные используются при загрузке объекта (десериализация или сопоставление объекта) и в тестах SQL-запросов (когда я хочу узнать, вернет ли код правильный результат).

    Плюс, у меня обычно есть некоторые магистральные данные (конфиг, таблицы поиска имен и т. Д.). Они также загружаются на этом этапе. Обратите внимание, что эта загрузка является одиночным тестом (наряду с созданием БД с нуля).

  • Когда у меня есть код, который изменяет БД (объект -> БД), я обычно запускаю его для живой БД (в памяти или где-то в тестовом экземпляре). Это должно гарантировать, что код работает; не создавать большого количества строк. После теста я выполняю откат транзакции (следуя правилу, согласно которому тесты не должны изменять глобальное состояние).

Конечно, есть исключения из правила:

  • Я также создаю большое количество строк в тестах производительности.
  • Иногда мне приходится фиксировать результат модульного теста (в противном случае тест становился бы слишком большим).
1 голос
/ 27 января 2009

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

Это просто и очень легко повторяется.

0 голосов
/ 27 января 2009

Это, вероятно, не ответит на все ваши вопросы, если таковые имеются, но я принял решение в одном проекте провести модульное тестирование на базе данных. В моем случае я чувствовал, что структура БД тоже нуждалась в тестировании, т. Е. Сделал ли мой дизайн БД то, что необходимо для приложения. Позже в проекте, когда я почувствую, что структура БД стабильна, я, вероятно, отойду от этого.

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

Причиной для этого во внешней программе было: 1. Я мог бы повторно запустить тесты, изменив данные теста, то есть удостовериться, что мои тесты были в состоянии выполнить несколько раз, и изменения данных, сделанные тестами, были действительными изменениями. 2. Я мог бы при необходимости почистить БД и начать все сначала.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...