Как TDD, чтобы верные результаты были возвращены

12

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

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

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

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

Я думал, я мог бы передать диапазон дат в Proc GetValidPO, и он использовал эти значения, чтобы вернуть действительные PO. Но что, если мы добавим еще одно требование к тому, что считается действительным PO?

И как я могу проверить это и убедиться, что он продолжает работать? Мы не используем ORM, и это вряд ли произойдет. И я не могу вызвать БД в моем тесте.

Я застрял.

Моя другая мысль: есть некоторые mocks, которые возвращают действительные данные, другие, которые возвращают некоторые неверные данные, и локальный репозиторий генерирует исключение, если возникают неверные данные, и проверяет, что исключение генерируется, если недействительные данные возвращаются процедурой GetValidPOs (или макет, используемый в тестировании).

Имеет ли это смысл? Или есть лучший способ?

ОБНОВЛЕНИЕ: я могу использовать EF, казалось бы. Теперь мне просто нужно выяснить, как его использовать, и сделать его тестируемым, при этом все еще имея возможность полагаться на хранимые процедуры и сложность разброса данных по нескольким базам данных.

CaffGeek
источник
Из любопытства, почему вы не можете выбрать только действительные PO с простым оператором SQL? (Этот вопрос или ответ не подразумевает решения.)
scarfridge

Ответы:

7

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

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

Вы по-прежнему можете выполнять код модульного тестирования, который требует данных, абстрагируя все, что нельзя выполнить модульным тестом (классы ADO.NET), в класс DAO, который затем можно смоделировать. Затем вы можете проверить, что ожидаемые вызовы сделаны, используя код, и воспроизвести реальное поведение (например, не найдя результатов), что позволяет тестировать различные варианты использования. Тем не менее, фактическая настройка SqlCommand для вызова хранимого процесса в значительной степени является последним, что вы можете выполнить модульным тестом, отделив создание команды от выполнения команды и издеваясь над исполнителем команды. Если это звучит как разделение интересов, это может быть; помните: «нет проблемы, которая не может быть решена другим слоем косвенности, за исключением слишком большого количества слоев косвенности». В какой-то момент вы должны сказать «достаточно; я просто не могу провести юнит-тестирование, мы

Другие опции:

  • Протестируйте сохраненный процесс, используя «недолговечный» экземпляр СУБД, такой как SQLite. Обычно это проще сделать при использовании ORM, но затем тест можно выполнить «в памяти» (или с предварительно установленным файлом базы данных, включенным в набор тестов). Все еще не является модульным тестом, но его можно запускать с высокой степенью изоляции (СУБД является частью запущенного процесса, а не того, к чему вы подключаетесь удаленно, что может быть в центре чьего-либо конфликтующего набора тестов). Недостатком является то, что изменения в хранимых процессах могут происходить в производственном процессе без тестирования, отражающего эти изменения, поэтому вам нужно быть дисциплинированным, чтобы убедиться, что изменения вначале были внесены в тестовую среду.

  • Рассмотрите возможность обновления до ORM. ORM с провайдером Linq (практически у всех таковых есть) позволит вам определить запрос как оператор Linq; этот оператор затем может быть передан в фиктивный репозиторий, в котором имеется коллекция тестовых данных в памяти для его применения. Таким образом, вы можете проверить правильность запроса, даже не касаясь БД (вам все равно следует выполнить запрос в среде интеграции, чтобы проверить, что поставщик Linq может правильно обработать запрос).

Keiths
источник
2
-1 потому что TDD! = Юнит тестирование. Прекрасно включать тесты уровня интеграции при выполнении TDD.
Стивен А. Лоу
Модульное тестирование - это подмножество тестовой разработки. В ходе разработки, основанной на тестировании, вы создадите «ходячий каркас» вашей системы, а затем запустите модульные, интеграционные и функциональные тесты в этой системе. Ваши интеграционные, модульные или приемочные тесты не пройдены, вы затем заставляете их проходить и пишете дальнейшие тесты
CodeART
1
Я все это понимаю, вы оба. Где я сказал, что это должен быть интеграционный тест, означающий, что вы не сможете его использовать? Моя точка зрения заключалась в том, что хранимая процедура не может быть протестирована изолированно, что вы хотите сделать с максимально возможной частью вашей кодовой базы. Тестирование SP вместо этого требует более сложных и длительных интеграционных тестов; Хотя они все же лучше, чем ручное тестирование, комплексный тестовый процесс может занять несколько часов и может оказать пагубное влияние на усилия по КИ.
KeithS
Тестирование SP часто также требует определенного набора данных в тестовой базе данных; код для приведения базы данных в надлежащее состояние для достижения ожидаемых результатов очень часто более LoC и в несколько раз дольше работает, чем код, который вы фактически используете. Это дополнительно усложняет временную сложность набора тестов, и часто настройку необходимо повторять для каждого отдельного теста (и, вероятно, их должно быть несколько для каждого SP, чтобы проверить, что каждое функциональное требование запроса в пределах удовлетворено).
KeithS
Хранимые процедуры можно тестировать изолированно. Как еще они будут проверены? Для Transact SQL есть tSQLt ( tsqlt.org )
Кевин Клайн
4

Мой совет - разделяй и властвуй . Забудьте на время о базе данных и постоянстве и сконцентрируйтесь на тестировании поддельных реализаций ваших репозиториев или объектов доступа к данным.

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

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

Я думал, я мог бы передать диапазон дат в Proc GetValidPO, и он использовал эти значения, чтобы вернуть действительные PO. Но что, если мы добавим еще одно требование к тому, что считается действительным PO?

Заглушите вызов GetValidPO, чтобы он вызывал вашу ложную реакцию, а не процедуру базы данных.

И как я могу проверить это и убедиться, что он продолжает работать? Мы не используем ORM, и это вряд ли произойдет. И я не могу вызвать БД в моем тесте.

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

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

Моя другая мысль: есть некоторые mocks, которые возвращают действительные данные, другие, которые возвращают некоторые неверные данные, и локальный репозиторий генерирует исключение, если возникают неверные данные, и проверяет, что исключение генерируется, если недействительные данные возвращаются процедурой GetValidPOs (или макет, используемый в тестировании).

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

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

CodeART
источник
Это то, что я пытаюсь сделать. Просто возникают трудности при написании реальной реализации, которая будет работать так же, как и макет, поскольку наш доступ к данным не способствует использованию ORM. Большая часть данных, которые мне нужны, хранятся в нескольких системах и должны быть доступны через веб-сервисы ... даже при обновлении.
CaffGeek
0

Точно так же, как модульное тестирование Java или Javascript означает написание модульных тестов с использованием языка Java для java, так и модульное тестирование функций Javascript с помощью Javascript, написание автоматизированных тестов, побуждающих вас писать хранимые процедуры, означает, что искомая библиотека модульных тестов основана на сохраненных процедуры.

Иными словами, используйте хранимые процедуры для тестирования хранимых процедур, потому что:

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

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

Lance Kind
источник