Как написать модульные тесты для вызовов базы данных
Я близок к началу нового проекта и (ах!) впервые я пытаюсь включить модульные тесты в свой проект.
У меня возникли проблемы с разработкой некоторых из модульных тестов сами. У меня есть несколько методов, которые были достаточно просты для тестирования (передать два значения и проверить ожидаемый результат). У меня есть другие части кода, которые делают более сложные вещи, как выполнение запросов к базе данных и я не уверен, как проверить их.
public DataTable ExecuteQuery(SqlConnection ActiveConnection, string Query, SqlParameterCollection Parameters)
{
DataTable resultSet = new DataTable();
SqlCommand queryCommand = new SqlCommand();
try
{
queryCommand.Connection = ActiveConnection;
queryCommand.CommandText = Query;
if (Parameters != null)
{
foreach (SqlParameter param in Parameters)
{
queryCommand.Parameters.Add(param);
}
}
SqlDataAdapter queryDA = new SqlDataAdapter(queryCommand);
queryDA.Fill(resultSet);
}
catch (Exception ex)
{
//TODO: Improve error handling
Console.WriteLine(ex.Message);
}
return resultSet;
}
этот метод по существу принимает все необходимые биты и фрагменты для извлечения некоторых данных из базы данных и возвращает данные в объекте DataTable.
первый вопрос, вероятно, самый сложный: что я должен даже проверить в такой ситуации?
Как только это будет решено, возникает вопрос о том, следует ли издеваться над компонентами базы данных или пытаться протестировать фактическую БД.
9 ответов:
Что вы тестируете?
есть три возможности, с моей головы:
A. вы тестируете класс DAO (объект доступа к данным), убедившись, что он правильно маршалирует значения/параметры, передаваемые в базу данных, и правильно маршалирует/преобразует/упаковывает результаты, полученные из базы данных.
в этом случае, вам не нужно подключаться к базе данных, у вас просто нужен модульный тест, который заменяет база данных (или промежуточный слой, например., JDBC, (N)Hibernate, iBatis) с макетом.
B. Вы проверяете синтаксическую корректность (сгенерированного) SQL.
в этом случае, поскольку диалекты SQL отличаются, вы хотите запустить (возможно, сгенерированный) SQL против правильной версии вашей СУБД, а не пытаться издеваться над всеми причудами вашей СУБД (и так, чтобы любые обновления СУБД, которые изменяют функциональность, были пойманы вашими тестами).
C. Вы тестируете семантический корректность вашего SQL, т. е. что для данного базового набора данных ваши операции (доступ/выбор и мутации/вставки и обновления) создают ожидаемый новый набор данных.
для этого вы хотите использовать что-то вроде dbunit (который позволяет вам настроить базовую линию и сравнить результирующий набор с ожидаемым результирующим набором), или, возможно, выполнить тестирование полностью в базе данных, используя метод, который я описываю здесь: лучший способ для проверки SQL запросов.
вот почему модульные тесты (IMHO) иногда могут создавать ложное чувство безопасности со стороны разработчиков. По моему опыту работы с приложениями, которые обращаются к базе данных, ошибки обычно являются результатом того, что данные находятся в неожиданном состоянии (необычные или отсутствующие значения и т. д.). Если вы регулярно моделируете доступ к данным в своих модульных тестах, вы будете думать, что ваш код отлично работает, когда он на самом деле все еще уязвим для такого рода ошибок.
Я думаю, что ваш лучший подход - это провести тест база данных удобна, заполнена кусками дерьмовых данных и запускает тесты компонентов базы данных против этого. Все это время помните, что ваши пользователи будут намного лучше, чем вы, при завинчивании ваших данных.
ради бога, не тестируйте против живой, уже заполненной базы данных. Но ты знал это.
в общем, у вас уже есть представление о том, какие данные будет извлекать каждый запрос, независимо от того, аутентифицируете ли вы пользователей, просматриваете записи в телефонной книге/организационной диаграмме или что-то еще. Вы знаете, какие поля вас интересуют, и вы знаете, какие ограничения существуют на них (например,
UNIQUE
,NOT NULL
и так далее). Вы модульное тестирование кода, который взаимодействует с базой данных, а не сама база данных, так что подумайте с точки зрения того, как проверить эти функции. Если это возможно для поляNULL
, вы должны иметь тест, который гарантирует, что ваш код обрабатываетNULL
неверно. Если одно из ваших полей является строкой (CHAR
,VARCHAR
,TEXT
, &c), проверьте, правильно ли вы обрабатываете экранированные символы.предположим, что пользователи будут пытаться поместить что-либо* в базу данных и создавать тестовые случаи соответственно. Вы хотите использовать макетные объекты для этот.
* включая нежелательные, вредоносные или недопустимые входные данные.
вы можете тестировать все, кроме : queryDA.Заполнить(результат);
Как только вы выполните queryDA.Заполните (resultSet), вы либо должны издеваться/подделывать базу данных, либо вы делаете интеграционное тестирование.
Я, например, не вижу, что интеграционное тестирование плохо, просто оно поймает другую ошибку, имеет разные шансы ложных негативов и ложных срабатываний, вряд ли это будет сделано очень часто, потому что это так медленно.
Если бы я был полностью тестируя этот код, я бы проверил, что параметры построены правильно, создает ли построитель команд правильное количество параметров? Все ли они имеют ценность? Правильно ли обрабатываются нули, пустые строки и DbNull?
на самом деле заполнение набора данных проверяет вашу базу данных, которая является слоеным компонентом из области вашего DAL.
строго говоря, тест, который записывает / читает из базы данных или файловой системы, не является модульным тестом. (Хотя это может быть интеграционный тест, и он может быть написан с использованием NUnit или JUnit). Модульные тесты предназначены для тестирования операций одного класса, изолируя его зависимости. Поэтому, когда вы пишете модульный тест для интерфейсного и бизнес-логического уровней, вам вообще не нужна база данных.
хорошо,но как вы тестируете уровень доступа к базе данных? Мне нравится совет от этого книга: тестовые Шаблоны xUnit (ссылка указывает на главу книги "тестирование w/ DB". Ключи:
- использовать тесты туда и обратно
- Не пишите слишком много тестов в вашем тестовом устройстве для доступа к данным, потому что они будут работать намного медленнее, чем ваши "реальные" модульные тесты
- если вы можете избежать тестирования с реальной базой данных, тест без базы данных
для модульных тестов я обычно издеваюсь или подделываю базу данных. Затем используйте свою фиктивную или поддельную реализацию через инъекцию зависимостей, чтобы проверить свой метод. У вас также, вероятно, есть некоторые интеграционные тесты, которые будут проверять ограничения, отношения внешнего ключа и т. д. в вашей базе данных.
Что касается того, что вы будете тестировать, вы должны убедиться, что метод использует соединение из параметров, что строка запроса назначена команде, и что ваш результирующий набор возвращается такой же, как что вы предоставляете через ожидание на метод заполнения. Примечание -- вероятно, проще протестировать метод Get, который возвращает значение, чем метод Fill, который изменяет параметр.
для того, чтобы сделать это правильно, хотя вы должны использовать некоторые инъекции зависимостей (DI), и для .NET есть несколько. В настоящее время я использую структуру Unity, но есть и другие, которые проще.
вот одна ссылка с этого сайта на эту тему, но есть и другие: инъекция зависимостей в .NET с примерами?
Это позволит вам более легко издеваться над другими частями вашего приложения, просто имея макет класса реализовать интерфейс, так что вы можете контролировать, как он будет реагировать. Но это также означает проектирование интерфейса.
поскольку вы спросили о лучших практиках, это будет один, ИМО.
затем, не идя в БД, если вам не нужно, как предложил другой.
Если вам нужно проверить определенные поведения, такие как отношения внешнего ключа с cascade delete, то вы можете написать тесты базы данных для этого, но, как правило, не собираетесь в реальную базу данных лучше, esp так как больше чем один человек может запустить модульный тест в то время, и если они собираются в те же тесты базы данных может произойти сбой, как ожидаемые данные могут измениться.
Edit: под модульным тестом базы данных я имею в виду это, поскольку он предназначен только для использования t-sql для выполнения некоторых настроек, тестирования и демонтажа. http://msdn.microsoft.com/en-us/library/aa833233%28VS.80%29.aspx
в проекте на основе JDBC соединение JDBC может быть издеваться, так что тесты могут выполняться без живых СУБД, причем каждый тестовый случай изолирован (без конфликта данных).
Это позволяет проверить, код персистентности передает правильные запросы / параметры (например https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/ParameterSpec.scala) и обрабатывать результаты JDBC (разбор/сопоставление), как ожидалось ("принимает все необходимые биты и куски, чтобы извлечь некоторые данные из базы данных и возвращает данные в объект DataTable").
рамки, такие как jOOQ или Acolyte, могут использоваться для:https://github.com/cchantep/acolyte .