Как применить TDD к функциям чтения / записи?

10

Это похоже на проблему курицы и яйца.

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

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

РЕДАКТИРОВАТЬ:

Я подключаюсь и выполняю транзакции с базой данных SQL для сохранения и загрузки объектов для использования. Нет смысла тестировать функции доступа, предоставляемые БД, но я обертываю такие функции БД для сериализации / десериализации объектов. Я хочу быть уверен, что я пишу и читаю правильные вещи в и из БД правильно.

Это не то же самое, что добавить / удалить, как упоминает @snowman. Я хочу знать, что написанное мной содержимое корректно, но для этого требуется хорошо протестированная функция чтения. Когда я читаю, я хочу быть уверенным, что мое чтение правильно создало объект, равный тому, что было написано; но для этого требуется хорошо протестированная функция записи.

user2738698
источник
Вы пишете свое собственное хранилище данных или используете уже существующее? Если вы используете существующий, предположите, что он уже работает. Если вы пишете свое собственное, это работает так же, как и любое другое программное обеспечение, использующее TDD.
Роберт Харви
1
Несмотря на тесную связь, я не думаю, что это дубликат этого конкретного вопроса. Цель dupe говорит о добавлении / удалении, это чтение / запись. Разница заключается в зависимости чтения / записи , вероятно , опирается на содержании объектов считывания / записи, а / удаление теста просто добавить, вероятно , будет гораздо проще: делает объект EXIST или нет.
2
Вы можете создать базу данных с данными и вообще без функции записи, чтобы протестировать функцию чтения.
JeffO

Ответы:

7

Начните с функции чтения.

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

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

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

Ewan
источник
Я думаю, вы могли бы создать БД в памяти, чтобы ускорить процесс, но это может быть слишком сложно. Почему бы не использовать mocks вместо этого в модульных тестах?
BЈовић
1
Немного адвокат Дьявола здесь, но как вы проверяете, что база данных была создана правильно? Как заявлено ОП, курица и яйцо.
user949300
1
@Ewan - я абсолютно согласен, что вы не должны проверять их код БД. Но откуда вы знаете, что ваш установочный код БД где-то не забыл INSERT или поставил неверное значение в столбце?
user949300
1
с чистого подхода TDD тест является требованием. так что это логически не может быть неправильно. ессно в реальном мире вы должны
Ewan
1
Quis Custodiet Ipsos Custodes? Или "Кто тестирует тесты?" :-) Я согласен с вами, что в пуристическом мире TDD это будет ужасно утомительным и подверженным ошибкам (особенно если бы это была сложная структура из нескольких таблиц с 8 JOINS) способом сделать это. Upvoted.
user949300
6

Я часто просто пишу, а потом читаю. например (псевдокод)

Foo foo1 = setup some object to write
File tempfile = create a tempfile, possibly in memory 
writeFoo(foo1, tempfile) 
Foo foo2 = readFoo(tempfile) 
assertEquals(foo1, foo2); 
clean-up goes here

Добавлено позже

Помимо того, что это решение является «prgamatic» и «достаточно хорошим», можно утверждать, что другие решения проверяют не то, что нужно . Проверка соответствия строк или операторов SQL не является ужасной идеей, я сделал это сам, но он проверяет побочный эффект и хрупок. Что если вы измените заглавные буквы, добавите поле или обновите номер версии в ваших данных? Что если ваш драйвер SQL переключает порядок вызовов для повышения эффективности, или ваш обновленный сериализатор XML добавляет дополнительное пространство или изменяет версию схемы?

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

user949300
источник
1
Потому что это действительно плотный псевдокод на 90%? Не уверен. Может быть, выделите текст и сделайте код менее шумным?
RubberDuck
1
Да, @ Иван. Фанатик нахмурился бы на это, однако прагматичный программист сказал бы «достаточно хорошо» и пошел дальше.
RubberDuck
1
Я вроде как прочитал вопрос как ... «при условии, что я следую TDD, как фанатик ...»
Ewan
1
Моя интерпретация OP и TDD заключается в том, что ваш тест должен быть написан первым, а не использовать как чтение, так и запись, если только один из них не был протестирован в другом месте.
Ewan
2
вы тестируете, «чтение должно возвращать то, что я пишу», но требования «чтение должно возвращать данные из БД» и «запись должна записывать данные в БД»
Ewan
4

Не. Не тестируйте модуль ввода / вывода. Это пустая трата времени.

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

Чтобы уточнить, если вы хотите протестировать HTTP-сервер, вы должны сделать это с помощью двух типов тестов: интеграционные тесты и модульные тесты. Модульные тесты вообще не должны взаимодействовать с I / O. Это медленно и создает много ошибок, которые не имеют ничего общего с правильностью вашего кода. Модульные тесты не должны зависеть от состояния вашей сети!

Ваш код должен отделиться:

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

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

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


Вот некоторые примеры:

  • Если вы пишете код, который получает данные из реляционной базы данных, вы можете выполнить модульное тестирование сопоставления данных, возвращенных из реляционных запросов, с моделью вашего приложения.
  • Если вы пишете код, который записывает данные в реляционную базу данных, вы можете модульно проверить, какие фрагменты данных вы хотите записать в базу данных, фактически не проверяя конкретные запросы SQL, которые вы используете. Например, вы можете хранить в памяти две копии состояния вашего приложения: копию, представляющую, как выглядит база данных, и рабочую копию. Если вы хотите синхронизировать данные с базой данных, вам нужно их разнести и записать различия в базу данных. Вы можете легко протестировать этот код различий.
  • Если вы пишете код, который читает что-то из файла конфигурации, вы хотите протестировать свой анализатор формата файла конфигурации, но со строками из вашего исходного файла теста, а не со строками, которые вы получаете с диска.
Майлз Рут
источник
2

Я не знаю, является ли это стандартной практикой или нет, но она прекрасно работает для меня.

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

Они могут быть легко проверены в изоляции:

 assertEquals("<xml><car type='porsche'>....", new Car("porsche").toString());

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

Кстати: что-то не так, если один тест проверяет чтение / запись вместе?

k3b
источник
Возможно, это нехорошо или «чисто», но это прагматичное решение.
RubberDuck
Мне тоже нравится идея тестирования чтения и записи вместе. Ваш toString () - хороший прагматичный компромисс.
user949300
1

Известные данные должны быть отформатированы известным способом. Самый простой способ реализовать это - использовать постоянную строку и сравнить результат, как описано в @ k3b.

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

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

BobDalgleish
источник
1

Используйте внедрение зависимости и насмешки.

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

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

В вашем рабочем коде передайте реальный объект базы данных.

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

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

Philipp
источник
Адвокат дьявола: Откуда вы знаете, какие операторы SQL он «должен получать»? Что если драйвер БД оптимизирует порядок по сравнению с тем, что указано в коде?
user949300
@ user949300 Макет объекта базы данных обычно заменяет драйвер базы данных.
Филипп
когда вы тестируете репозиторий, нет смысла вставлять в клиент фиктивную базу данных. Вы должны проверить, что ваш код запускает sql, который работает с базой данных. в противном случае вы заканчиваете, просто тестируя свой макет
Ewan
@Ewan Это не то, что такое юнит-тестирование. Модульный тест проверяет одну единицу кода, изолированную от остального мира. Вы не тестируете взаимодействия между компонентами, такими как ваш код и база данных. Вот для чего нужно интеграционное тестирование.
Филипп
да. Я говорю, что нет смысла модульное тестирование хранилища БД. единственное, что стоит сделать
Ewan
0

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

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

Если вы следуете простейшей версии шаблонов Repository и Unit of Work и тестируете все свои сопоставления, вы можете рассчитывать на то, что все работает очень хорошо.

pnschofield
источник