Создание модульных тестов на уровне приложения CRUD, как я могу сделать тесты независимыми?

14

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

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

И для теста удаления мне, очевидно, нужно создать фиктивный объект, чтобы я мог его удалить.

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

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

Как такие случаи должны рассматриваться?

рукав моря
источник
Вы также можете взглянуть на этот вопрос: programmers.stackexchange.com/questions/115455/…
Guven

Ответы:

13

Ну, нет ничего плохого в том, что ты делаешь. Несколько тестов могут охватывать один и тот же код; это просто означает, что одна проблема приведет к сбою нескольких тестов. Чего вы хотите избежать, так это тестов, которые зависят от результатов других тестов. То есть, ваш тест на удаление зависит от того, был ли запущен тест на добавление, и, таким образом, если вы выполните тест на удаление ДО вашего теста на добавление, он потерпит неудачу. Чтобы избежать этой проблемы, убедитесь, что у вас есть «чистый лист» в начале каждого теста, чтобы то, что происходит в данном тесте, не могло повлиять на последующие тесты.

Отличный способ сделать это - запустить тесты для базы данных в памяти.

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

В своем тесте на удаление создайте базу данных, в которой уже будет удален объект. Удалите объект и подтвердите, что он был удален.

Унесите базу данных в свой код.

Адам Яскевич
источник
База данных в памяти просто быстрая (в памяти) и простая (в процессе). Вы можете сделать это с любым хранилищем данных.
Пол Дрейпер
3

Используйте транзакции.

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

Да, чтобы отменить заказ, вам сначала нужно будет его создать. Все в порядке. Тест сначала создает заказ, затем отменяет его, а затем проверяет, отменен ли заказ.

Кевин Клайн
источник
Люблю эту идею. Реализовано это сегодня с большим эффектом.
pimbrouwers
3

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

Килиан Фот
источник
2

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

Я думаю, что вы находитесь в ситуации, когда вы видели некоторые рекомендации для модульных тестов, которые говорят такие вещи, как

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

и так далее. И все это правильно, в зависимости от того, как вы определяете «модульный тест» .

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

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

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

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

Но, прежде всего, понимайте, что существует много видов программных тестов (модульные тесты, системные тесты, интеграционные тесты, предварительные тесты и т. Д.), И не пытайтесь применять руководство одного типа ко всем остальным.

Эрик Кинг
источник
Так вы говорите, что не можете удалить модульный тест из базы данных?
ChrisF
Если вы попали в базу данных, это (по определению) интеграционный тест, а не модульный тест. Так что в этом смысле нет. Вы не можете удалить модульный тест из базы данных. Что вы можете модульное тестирование является то , что , когда код , который вы тестируете просит удалить некоторые данные, он взаимодействует с модулем доступа к данным правильно.
Эрик Кинг,
Но дело в том, что некоторые люди могут определять «модульный тест» по-разному, поэтому мы должны быть осторожны при применении руководства «модульный тест», так как руководство может не применяться так, как мы думаем.
Эрик Кинг,
1

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

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

unholysampler
источник
1

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

Так?

... не противоречит ли это правилам модульного тестирования?

Нет.

Как такие случаи должны рассматриваться?

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

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

С. Лотт
источник
1

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

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

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