Модульные тесты - преимущество от модульных тестов с изменениями контракта?

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

Возможно, любой может enlight меня, как приблизиться к этой проблеме. Позвольте мне уточнить:

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

У меня есть тесты контракта, кто тестирует это. И во всех моих других тестах я блокирую эту изящную штуку калькулятора.

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

Мои тесты контракта перестанут работать, и я зафиксирую их соответственно. Но, все, мой дразнил/блокировал объекты, будут все еще использовать старые правила контракта. Эти тесты успешно выполнятся, в то время как они не должны!

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

Как Вы думаете об этом случае? Я никогда не думал об этом thourougly. По-моему, эти изменения в модульных тестах были бы приемлемы. Если бы я не использую модульные тесты, я также видел бы, что такие ошибки возникают в фазе тестирования (тестерами). Все же я не достаточно уверен указать на то, что будет стоить большего количества времени (или меньше).

Какие-либо мысли?

51
задан skaffman 18 February 2012 в 23:08
поделиться

7 ответов

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

Некоторые простые вещи, которые вызывают такую хрупкость:

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

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

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

Тесты должны быть организованы следующим образом:

  • Юнит-тесты обеспечивают близкое к 100% покрытие кода. Они тестируют независимые блоки. Они пишутся программистами, использующими язык программирования системы.
  • Компонентные тесты покрывают ~50% системы. Они пишутся бизнес-аналитиками и QA. Они пишутся на таких языках, как FitNesse, Selenium, Cucumber и др. Они тестируют целые компоненты, а не отдельные блоки. Они тестируют в основном счастливые случаи пути и некоторые хорошо заметные несчастливые случаи пути.
  • Интеграционные тесты охватывают ~20% системы. Они тестируют небольшие сборки компонентов, а не всю систему. Также пишутся на FitNesse/Selenium/Cucumber и т.д. Написаны архитекторами.
  • Системные тесты охватывают ~10% системы. Они тестируют всю систему, интегрированную вместе. Опять же, они пишутся на FitNesse/Selenium/Cucumber и т.д. Пишутся архитекторами.
  • Исследовательские ручные тесты. (См. Джеймс Бах) Эти тесты проводятся вручную, но не по сценарию. В них используется человеческая изобретательность и творческий подход.
92
ответ дан 7 November 2019 в 09:57
поделиться

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

  1. Приемочные тесты - тестируйте пару или более классов. Эти тесты обычно согласованы с пользовательскими хранилищами, которые необходимо реализовать, поэтому вы проверяете, что пользовательская история «работает». Им не нужно подключаться к БД или другим внешним системам, но может.
  2. Интеграционные тесты - в основном для проверки подключения к внешней системе и т. Д.
  3. Полные сквозные тесты - тестируют всю систему

Обратите внимание, что даже если у вас есть 100% покрытие модульными тестами, вы даже не гарантировано, что ваше приложение запустится! Вот почему вам нужны тесты более высокого уровня. Существует так много разных уровней тестов, потому что чем ниже вы что-то тестируете, тем обычно это дешевле (с точки зрения разработки, поддержки инфраструктуры тестирования, а также времени выполнения).

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

4
ответ дан 7 November 2019 в 09:57
поделиться

Юнит-тесты, конечно, не могут отловить все ошибки, даже в идеальном случае 100% покрытия кода/функциональности. Я думаю, что этого и не следует ожидать.

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

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

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

5
ответ дан 7 November 2019 в 09:57
поделиться

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

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

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

12
ответ дан 7 November 2019 в 09:57
поделиться

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

0
ответ дан 7 November 2019 в 09:57
поделиться

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

1
ответ дан 7 November 2019 в 09:57
поделиться

Одно из правил для кода юнит-тестов (и всего остального кода, используемого для тестирования) - относиться к нему так же, как и к производственному коду - не больше, не меньше - просто одинаково.

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

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

2
ответ дан 7 November 2019 в 09:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: