Как написать модульные тесты для вызовов базы данных - PullRequest
59 голосов
/ 02 августа 2009

Я близок к началу нового проекта и (задыхаюсь!) Впервые пытаюсь включить модульные тесты в мой проект.

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

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 ]

43 голосов
/ 02 августа 2009

Что вы тестируете?

У меня в голове три возможности:

A. Вы тестируете класс DAO (объект доступа к данным), проверяя, правильно ли он маршалирует значения / параметры, передаваемые в базу данных, и правильно ли марширует / преобразовывает / упаковывает результаты, полученные из базы данных.

В этом случае вам вообще не нужно подключаться к базе данных; вам просто нужен модульный тест, который заменяет базу данных (или промежуточный уровень, например, JDBC, (N) Hibernate, iBatis) на макет.

B. Вы проверяете синтаксическую правильность (сгенерированного) SQL.

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

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

Для этого вы хотите использовать что-то вроде dbunit (которое позволяет вам установить базовую линию и сравнить набор результатов с ожидаемым набором результатов), или, возможно, полностью выполнить тестирование в базе данных, используя технику, которую я здесь изложил : Лучший способ протестировать SQL-запросы .

28 голосов
/ 02 августа 2009

Вот почему (ИМХО) модульные тесты могут иногда создавать ложное чувство безопасности со стороны разработчиков. По моему опыту работы с приложениями, которые обращаются к базе данных, ошибки обычно являются результатом непредвиденного состояния данных (необычные или отсутствующие значения и т. Д.). Если вы регулярно макетируете доступ к данным в своих модульных тестах, вы будете думать, что ваш код работает отлично, хотя на самом деле он все еще уязвим для такого рода ошибок.

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

10 голосов
/ 03 августа 2009

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

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

6 голосов
/ 02 августа 2009

Ради любви к Богу, не проверяйте живую, уже заполненную базу данных. Но ты это знал.

Как правило, у вас уже есть представление о том, какие данные будет собирать каждый запрос, независимо от того, проводите ли вы аутентификацию пользователей, просматриваете записи в телефонной книге / диаграмме организации или что-то еще. Вы знаете, какие области вас интересуют, и знаете, какие ограничения существуют для них (например, UNIQUE, NOT NULL и т. Д.). Вы проводите модульное тестирование своего кода, который взаимодействует с базой данных, а не с самой базой данных, поэтому подумайте о том, как протестировать эти функции. Если для поля возможно значение NULL, у вас должен быть тест, который гарантирует, что ваш код правильно обрабатывает значения NULL. Если одно из ваших полей является строкой (CHAR, VARCHAR, TEXT, & c), проверьте, правильно ли вы обрабатываете экранированные символы.

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

* Включая нежелательные, злонамеренные или недействительные данные.

4 голосов
/ 02 августа 2009

Вы можете протестировать все, кроме: queryDA.Fill(resultSet);

Как только вы выполните queryDA.Fill(resultSet), вам придется либо смоделировать / подделать базу данных, либо проводить интеграционное тестирование.

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

Если бы я проводил модульное тестирование этого кода, я бы проверил правильность построения параметров. Создает ли компоновщик команд правильное количество параметров? У них всех есть ценность? Правильно ли обрабатываются нули, пустые строки и DbNull?

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

3 голосов
/ 02 августа 2009

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

ОК, но как вам выполнить юнит-тестирование уровня доступа к базе данных? Мне нравится совет из этой книги: xUnit Test Patterns (ссылка указывает на главу книги "Тестирование с базой данных". Ключи:

  • используйте тесты туда и обратно
  • не пишите слишком много тестов в своем приборе для тестирования доступа к данным, потому что они будут работать намного медленнее, чем ваши "настоящие" модульные тесты
  • если вы можете избежать тестирования с реальной базой данных, тестируйте без базы данных
2 голосов
/ 02 августа 2009

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

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

1 голос
/ 02 августа 2009

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

Вот одна ссылка с этого сайта на эту тему, но есть и другие: Внедрение зависимостей в .NET с примерами?

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

Поскольку вы спрашивали о передовой практике, это будет один, ИМО.

Тогда не ходите в БД, если вам не нужно, как это и предлагается.

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

Редактировать: Под модульным тестом базы данных я имею в виду это, так как он предназначен только для использования t-sql для некоторой настройки, тестирования и демонтажа. http://msdn.microsoft.com/en-us/library/aa833233%28VS.80%29.aspx

0 голосов
/ 15 января 2014

В проекте, основанном на 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.

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