Сегодня я смотрел видео " Основы JUnit ", и автор сказал, что при тестировании определенного метода в вашей программе вы не должны использовать другие ваши собственные методы в этом процессе.
Чтобы быть более конкретным, он говорил о тестировании некоторого метода создания записи, который принимает имя и фамилию для аргументов, и он использовал их для создания записей в данной таблице. Но он утверждал, что в процессе тестирования этого метода он не должен использовать свои другие методы DAO для запроса базы данных, чтобы проверить конечный результат (чтобы убедиться, что запись действительно была создана с правильными данными). Он утверждал, что для этого он должен написать дополнительный код JDBC для запроса к базе данных и проверки результата.
Я думаю, что понимаю смысл его утверждения: вы не хотите, чтобы контрольный пример одного метода зависел от правильности других методов (в данном случае, метода DAO), и это достигается написанием (снова) вашей собственной проверки / поддерживающий код (который должен быть более конкретным и сфокусированным, следовательно, более простым кодом).
Тем не менее, голоса в моей голове начали протестовать с такими аргументами, как дублирование кода, ненужные дополнительные усилия и т. Д. Я имею в виду, если мы запускаем весь тестовый аккумулятор и тщательно проверяем все наши публичные методы (включая метод DAO в данном случае), Разве можно использовать некоторые из этих методов при тестировании других методов? Если один из них не делает то, что должен, его собственный тестовый случай не пройден, и мы можем исправить это и снова запустить тестовую батарею. Нет необходимости дублировать код (даже если дублирующий код несколько проще) или тратить усилия впустую.
У меня есть сильное чувство по этому поводу из-за нескольких недавних приложений Excel - VBA, которые я написал (должным образом проверенный модулем благодаря Rubberduck для VBA ), где применение этой рекомендации означало бы много дополнительной дополнительной работы, без видимой выгоды.
Можете ли вы поделиться своим мнением об этом?
источник
Ответы:
Дух его заявления действительно верен. Суть модульных тестов состоит в том, чтобы изолировать код, протестировать его без зависимостей, чтобы любое ошибочное поведение можно было быстро распознать в том месте, где оно происходит.
С учетом сказанного, модульное тестирование - это инструмент, и оно предназначено для ваших целей, это не алтарь, которому нужно молиться. Иногда это означает, что нужно оставить зависимости, потому что они работают достаточно надежно, и вы не хотите их издеваться, иногда это означает, что некоторые из ваших модульных тестов на самом деле довольно близки, если не являются интеграционными тестами.
В конечном счете, вы не получаете оценку, важно, каков конечный продукт тестируемого программного обеспечения, но вам просто нужно помнить о том, когда вы нарушаете правила и решаете, когда компромиссы того стоят.
источник
Я думаю, что это сводится к терминологии. Для многих «модульный тест» - это очень специфическая вещь, и по определению он не может иметь условие прохождения / отказа, которое зависит от любого кода вне тестируемого модуля (метода, функции и т. Д.). Это будет включать взаимодействие с базой данных.
По мнению других, термин «модульное тестирование» гораздо более свободен и охватывает любой вид автоматического тестирования, включая тестовый код, который тестирует интегрированные части приложения.
Пурист теста (если я могу использовать этот термин) может называть это интеграционным тестом , который отличается от модульного теста на основании того, что тест зависит от более чем одной чистой единицы кода.
Я подозреваю, что вы используете более слабую версию термина «модульный тест» и на самом деле имеете в виду «интеграционный тест».
Используя эти определения, вы не должны зависеть от кода вне тестируемого модуля в «модульном тесте». Однако в «интеграционном тесте» совершенно разумно написать тест, который выполняет какой-то конкретный фрагмент кода, а затем проверяет базу данных на соответствие критериям.
Должны ли вы зависеть от интеграционных тестов или модульных тестов, или от того и другого - гораздо более обширная тема для обсуждения
источник
Ответ да, и нет...
Уникальные тесты, которые выполняются изолированно, абсолютно необходимы, поскольку они позволяют заложить основу для правильной работы низкоуровневого кода. Но при кодировании больших библиотек вы также найдете области кода, которые потребуют проведения тестов, которые пересекают модули.
Эти кросс-модульные тесты хороши для покрытия кода и при тестировании сквозной функциональности, но они имеют ряд недостатков, о которых вам следует знать:
В конце дня вы захотите провести несколько таких тестов: множество тестов, которые улавливают низкоуровневую функциональность, и несколько тестов, которые тестируют сквозную функциональность. Сосредоточьтесь на том, почему вы пишете тесты в первую очередь. Они здесь, чтобы дать вам уверенность в том, что ваш код работает так, как вы ожидаете. Все, что вам нужно сделать, чтобы это произошло, просто прекрасно.
Печальный, но верный факт: если вы вообще используете автоматизированные тесты, значит, вы уже знакомы со многими разработчиками. Надлежащая практика тестирования - это первое, что нужно сделать, когда команда разработчиков сталкивается с жесткими сроками. Поэтому, пока вы придерживаетесь своего оружия и пишете модульные тесты, которые являются значимым отражением того, как должен работать ваш код, я бы не стал зацикливаться на том, насколько «чисты» ваши тесты.
источник
Одна из проблем, связанных с использованием других методов объекта для проверки условия, заключается в том, что вы пропустите ошибки, которые взаимно компенсируют друг друга. Что еще более важно, вы упускаете боль из-за сложной реализации теста, и эта боль научит вас кое-чему о вашем базовом коде. Болезненные тесты теперь означают болезненное обслуживание позже.
Сделайте ваши юниты меньше, разделите ваш класс, реорганизуйте и измените дизайн, пока не станет проще тестировать, не реализуя остальную часть вашего объекта. Получите помощь, если считаете, что ваш код или тесты не могут быть упрощены в дальнейшем. Попробуйте подумать о коллеге, который, кажется, всегда удачлив и получает задания, которые легко проверить безошибочно. Попросите его или ее о помощи, потому что это не удача.
источник
Если тестируемой единицей функциональности является «данные хранятся постоянно и могут быть извлечены», то я хотел бы провести тест модульного теста, который - сохранит данные в реальную базу данных, уничтожит все объекты, содержащие ссылки на базу данных, а затем вызовет код для извлечения объекты назад.
Проверка того, создана ли запись в базе данных, похоже, связана с деталями реализации механизма хранения, а не с проверкой функциональности, доступной для остальной части системы.
Возможно, вы захотите сократить тест для повышения производительности, используя фиктивную базу данных, но у меня были подрядчики, которые сделали именно это и оставили в конце своего контракта систему, которая прошла такие тесты, но на самом деле ничего не сохранила в базе данных. между перезагрузками системы.
Вы можете утверждать, означает ли «модуль» в модульном тесте «единицу кода» или «единицу функциональности», функциональность, возможно, созданную многими единицами кода совместно. Я не считаю это полезным различием: я хотел бы иметь в виду следующие вопросы: «говорит ли тест о том, обеспечивает ли система ценность для бизнеса», и «является ли тест хрупким, если реализация изменится?». Тесты, подобные описанному, полезны, когда вы работаете с системой TDD - вы еще не написали «получить объект из записи базы данных», поэтому не можете протестировать всю функциональность - но хрупки к изменениям реализации, поэтому я бы удалите их, как только полная операция будет проверена.
источник
Дух правильный.
В идеале, в модульном тесте вы тестируете модуль (индивидуальный метод или небольшой класс).
В идеале вы бы заглушили всю систему баз данных. Т.е. вы должны запустить свой метод в фальшивой среде и просто убедиться, что он вызывает правильные API БД в правильном порядке. Вы явно, безусловно, не хотите тестировать БД при тестировании одним из ваших собственных методов.
Преимуществ много. Прежде всего, тесты быстро ослепляют, потому что вам не нужно беспокоиться о настройке правильной среды БД и ее откате впоследствии.
Конечно, это высокая цель в любом программном проекте, который не делает это с самого начала. Но это дух модульного тестирования.
Обратите внимание, что существуют другие тесты, такие как функциональные тесты, тесты поведения и т. Д., Которые отличаются от этого подхода - не путайте «тестирование» с «модульным тестированием».
источник
Есть по крайней мере одна очень важная причина не использовать прямой доступ к базе данных в своих модульных тестах для бизнес-кода, который взаимодействует с базой данных: если вы измените реализацию своей базы данных, вам придется переписать все эти модульные тесты. Переименуйте столбец, для вашего кода вам просто нужно изменить одну строку в вашем определении отображения данных. Однако, если вы не используете свой преобразователь данных во время тестирования, вы обнаружите, что вам также необходимо изменить каждый отдельный модульный тест, который ссылается на этот столбец . Это может превратиться в невероятный объем работы, особенно для более сложных изменений, которые менее поддаются поиску и замене.
Кроме того, использование преобразователя данных в качестве абстрактного механизма вместо непосредственного общения с базой данных упрощает полное удаление зависимости от базы данных , что может быть неактуально сейчас, но когда вы получаете до тысячи модульных тестов и все они попали в базу данных, вы будете благодарны, что вы можете легко реорганизовать их, чтобы удалить эту зависимость, потому что ваш набор тестов откажется от нескольких минут до нескольких секунд, что может иметь огромное преимущество для вашей производительности.
Ваш вопрос указывает на то, что вы думаете по правильному пути. Как вы подозреваете, модульные тесты - это тоже код. Столь же важно сделать их обслуживаемыми и легко адаптируемыми к будущим изменениям, как и для остальной части вашего кода. Старайтесь, чтобы они были удобочитаемыми и устраняли дублирование, и у вас будет гораздо лучший набор тестов для этого. И хороший набор тестов - это тот, который привыкает. А набор тестов, который привыкает, помогает находить ошибки, в то время как тот, который не используется, просто бесполезен.
источник
Лучший урок, который я усвоил, изучая модульное тестирование и интеграционное тестирование, - это не тестирование методов, а тестирование поведения. Другими словами, что делает этот объект?
Когда я смотрю на это таким образом, метод, который сохраняет данные, и другой метод, который читает их обратно, начинают тестироваться. Конечно, если вы тестируете методы конкретно, вы в конечном итоге получаете тестирование для каждого метода отдельно, например:
Эта проблема возникает из-за перспективы методов тестирования. Не проверяйте методы. Тест поведения. Каково поведение класса WidgetDao? Сохраняются виджеты. Хорошо, как вы убедитесь, что виджеты сохраняются? Ну, каково определение постоянства? Это означает, что когда вы пишете это, вы можете прочитать его снова. Так что чтение + запись вместе становятся тестом, и, на мой взгляд, более значимым тестом.
Это логичный, сплоченный, надежный и, на мой взгляд, значимый тест.
Другие ответы фокусируются на том, насколько важна изоляция, прагматичные и реалистичные дебаты, и может ли юнит-тест запрашивать базу данных или нет. Те, на самом деле не отвечают на вопрос, хотя.
Чтобы проверить, можно ли что-то сохранить, вы должны сохранить его и затем прочитать обратно. Вы не можете проверить, сохранено ли что-то, если вам запрещено читать это. Не проверяйте хранение данных отдельно от поиска данных. Вы закончите с тестами, которые ничего вам не говорят.
источник
Одним из примеров случая сбоя, который вы хотите предварительно отловить, является то, что тестируемый объект использует слой кэширования, но не может сохранить данные по мере необходимости. Затем, если вы запросите объект, он скажет: «Да, у меня есть новое имя и адрес», но вы хотите, чтобы тест не прошел, потому что он на самом деле не сделал то, что должен был.
В качестве альтернативы (и пропуская нарушение единственной ответственности), предположим, что требуется сохранить версию строки в кодировке UTF-8 в байтово-ориентированном поле, но на самом деле сохраняется Shift JIS. Какой-то другой компонент собирается прочитать базу данных и ожидает увидеть UTF-8, отсюда и требование. Затем в оба конца этот объект сообщит правильное имя и адрес, потому что он преобразует его обратно из Shift JIS, но ошибка не обнаружена вашим тестом. Надеемся, что это будет обнаружено в более позднем интеграционном тесте, но весь смысл модульных тестов состоит в том, чтобы выявлять проблемы на ранних этапах и точно знать, какой компонент отвечает.
Вы не можете этого допустить, потому что если вы не будете осторожны, вы напишите взаимозависимый набор тестов. "Это экономит?" test вызывает метод save, который тестирует, а затем метод load, чтобы подтвердить сохранение. "Это загружается?" test вызывает метод save для настройки тестового прибора, а затем метод загрузки, который он тестирует, чтобы проверить результат. Оба теста основаны на правильности метода, который они не тестируют, что означает, что ни один из них на самом деле не проверяет правильность метода, который он тестирует.
Подсказка, что здесь есть проблема, состоит в том, что два теста, которые предположительно тестируют разные модули, на самом деле делают одно и то же . Они оба вызывают установщик, за которым следует получатель, а затем проверяют, является ли результат исходным значением. Но вы хотели проверить, что установщик сохраняет данные, а не то, что пара установщик / получатель работает вместе. Итак, вы знаете, что что-то не так, вам просто нужно выяснить, что и исправить тесты.
Если ваш код хорошо спроектирован для модульного тестирования, то есть как минимум два способа проверить, действительно ли данные были правильно сохранены тестируемым методом:
смоделируйте интерфейс базы данных, и сделайте, чтобы ваша ложная запись запомнила тот факт, что для него были вызваны надлежащие функции с ожидаемыми значениями. Этот тест проверяет, что метод делает то, что должен, и является классическим модульным тестом.
передать ему фактическую базу данных с точно таким же намерением , чтобы записать, правильно ли были сохранены данные. Но вместо того, чтобы иметь поддельную функцию, которая просто говорит: «Да, я получил правильные данные», ваш тест считывает данные из базы данных напрямую и подтверждает, что это правильно. Возможно, это не самый чистый тест, потому что весь движок базы данных довольно полезен для написания прославленного макета, и у меня больше шансов пропустить какую-то тонкость, которая делает тест пройденным, даже если что-то не так (например, я не должен использовать то же соединение с базой данных для чтения, которое использовалось для записи, потому что я могу увидеть незафиксированную транзакцию). Но это проверяет правильность, и, по крайней мере, вы знаете, что это точно реализует весь интерфейс базы данных без необходимости писать какой-либо фиктивный код!
Так что это просто деталь реализации теста, считываю ли я данные из тестовой базы данных JDBC или я имитирую базу данных. В любом случае дело в том, что я могу лучше протестировать модуль, изолировав его, чем смогу, если позволю ему сговориться с другими неправильными методами в том же классе, чтобы они выглядели правильно, даже если что-то не так. Поэтому я хочу использовать любые удобные средства для проверки правильности сохраненных данных, за исключением доверия к компоненту, метод которого я тестирую.
Если ваш код плохо спроектирован для модульного тестирования, у вас может не быть выбора, потому что объект, метод которого вы хотите протестировать, может не принять базу данных как внедренную зависимость. В этом случае дискуссия о том, как лучше изолировать тестируемый блок, переходит в дискуссию о том, насколько близко можно добраться до изоляции тестируемого блока. Вывод тот же, хотя. Если вы можете избежать заговоров среди неисправных модулей, тогда вы делаете это, при условии наличия свободного времени и всего, что, по вашему мнению, будет более эффективным для обнаружения ошибок в коде.
источник
Вот как мне нравится это делать. Это просто личный выбор, так как это не повлияет на результат продукта, а только на способ его производства.
Это зависит от возможности добавлять фиктивные значения в вашу базу данных без приложения, которое вы тестируете.
Допустим, у вас есть два теста: A - чтение базы данных B - вставка в базу данных (зависит от A)
Если A проходит, то вы уверены, что результат B будет зависеть от части, протестированной в B, а не от зависимостей. Если A терпит неудачу, вы можете иметь ложный отрицательный результат для B. Ошибка может быть в B или в A. Вы не можете знать наверняка, пока A не вернет успех.
Я бы не стал писать некоторые запросы в модульном тестировании, поскольку вы не должны знать структуру БД, стоящую за приложением (или она может измениться). Это означает, что ваш тестовый пример может быть неудачным из-за самого кода. Если вы не пишете тест для теста, но кто будет тестировать тест для теста ...
источник
В конце концов, все сводится к тому, что вы хотите проверить.
Иногда достаточно проверить предоставленный интерфейс вашего класса (например, запись создается и сохраняется и может быть получена позже, возможно, после «перезапуска»). В этом случае хорошо (и обычно хорошая идея) использовать предоставленные методы для тестирования. Это позволяет повторно использовать тесты, например, если вы хотите изменить механизм хранения (другую базу данных, базу данных в памяти для тестирования, ...).
С другой стороны, в некоторых случаях вам нужно на самом деле проверить, как вещи сохраняются (может быть, какой-то другой процесс полагается на данные в нужном формате в этой базе данных?). Теперь вы не можете просто полагаться на получение правильной записи из класса, вместо этого вы должны убедиться, что она правильно хранится в базе данных. И самый прямой способ сделать это - запрос самой базы данных.
источник