Должны ли частные / защищенные методы проходить модульное тестирование?

83

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

Теперь мой вопрос касается частных и защищенных методов, которые мне, возможно, придется написать в моем классе для поддержки методов / свойств, предоставляемых интерфейсом:

  • Должны ли частные методы в классе иметь свои собственные модульные тесты?

  • Должны ли защищенные методы в классе иметь свои собственные модульные тесты?

Мои мысли:

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

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

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

Радж Рао
источник
1
Если вы не используете свои защищенные методы из своих тестов, переопределяя их или вызывая их, почему они защищены, а не закрыты? Делая их защищенными, вы принимаете сознательное решение раскрыть точку / функциональность расширения. На мой взгляд, если вы следуете TDD, это решение должно быть обусловлено написанными вами тестами.
forsvarir 09
2
Вы должны поместить часть о своих мыслях в отдельный ответ. Дайте мне знать, когда вы это сделаете, и я проголосую за.
Кейт Пинсон
То же самое только для частного: stackoverflow.com/questions/105007/…
Ciro Santilli 郝海东 冠状 病 六四 事件
Вы правы насчет активных модульных тестов, то есть тех, которые настроены на непрерывное выполнение. Для этого вам нужно протестировать только общедоступные и защищенные интерфейсы. Вы можете получить выгоду от написания тестов для частных методов. Эти тесты не должны быть частью вашего непрерывного набора, но как одноразовая проверка правильности вашей реализации, они могут быть очень ценным инструментом.
Дидье А.

Ответы:

109

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

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

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


источник
23
Мне нравится, что вы сказали, что юнит-тесты проверяют поведение, а не методы! Это многое проясняет.
Радж Рао,
1
Я согласен с @rajah. Это должно быть первое утверждение в каждом уроке. Мне было интересно, как проверить мои методы, теперь я знаю, что мне это не нужно. +1
frostymarvelous
3
Можно ли сказать, что это все еще применяется в тех случаях, когда базовые классы реализуют защищенное поведение, которое, как ожидается, будет наследовать и использовать публика? Тогда защищенные методы все еще являются частью публичного интерфейса, не так ли?
Ник Уделл
1
Вообще говоря, шаблоны, которые способствуют разделению проблем, больше подходят для изолированных модульных тестов, в то время как шаблоны, которые поддерживают инкапсуляцию, проще в использовании API.
尤川豪
3
Это не проясняет случай защищенной видимости. Кажется, что защищенный метод также является частью интерфейса, часто это точка расширения, специально защищенная, чтобы быть таковой. Я бы сказал, что в этих случаях вам также следует провести модульное тестирование. Вы же не хотите, чтобы в будущем кто-то что-то менял и ломал классы, поведение которых зависело от этих точек расширения.
Дидье А.
47

Я не согласен с большинством плакатов.

Самое важное правило: РАБОЧИЙ КОД ПРЕПЯТСТВУЕТ ТЕОРЕТИЧЕСКИМ ПРАВИЛАМ о публичном / защищенном / частном.

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

Если вы не можете, то либо проведите рефакторинг, чтобы вы могли, либо измените защищенные / частные правила.

Есть отличная история о психологе, который проводил детям тест. Он дал каждому ребенку две деревянные доски с веревкой, прикрепленной к каждому концу, и попросил их пересечь комнату, не касаясь ногами пола, как можно быстрее. Все дети использовали доски как маленькие лыжи, ставя одну ногу на каждую доску, держа их за веревки и скользя по полу. Затем он дал им такое же задание, но с использованием только ОДНОЙ доски. Они поворачивались / «шли» по полу, по одной ноге на каждом конце единой доски - и они были БЫСТРЕЕ!

Тот факт, что Java (или любой другой язык) имеет функцию (частную / защищенную / общедоступную), не обязательно означает, что вы пишете лучший код, потому что вы его используете!

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

Контекст также имеет значение. Если вы пишете API для использования посторонними людьми, важнее общедоступный / частный. Если это внутренний проект - кого это волнует?

Но в конце концов подумайте о том, сколько ошибок было вызвано отсутствием тестирования. Затем сравните с тем, сколько ошибок было вызвано «слишком заметными» методами. Этот ответ должен повлиять на ваше решение.

Чарльз Рот
источник
3
Если метод важен и имеет сложную логику, утверждение его поведения очень полезно для предотвращения ошибок. Написание модульного теста для такого метода может даже помочь, поскольку вы реализуете метод своего рода исследовательским способом. Так что, даже если он частный, я бы сказал, что стоит модульное тестирование. НО, и есть большое но, вы должны помнить, что тесты - это связывание кода. Если вы пишете тест для метода, вы предотвращаете рефакторинг.
Дидье А.
6
Поэтому, прежде чем приступить к написанию тестов для частных методов, я бы посоветовал всегда переосмысливать свой дизайн. Посмотрите, можно ли что-то обобщить и превратить в чисто функциональные методы. Если так, вы можете извлечь их в отдельную конструкцию. Затем эта конструкция может иметь собственный общедоступный интерфейс и проходить модульное тестирование. Помните, что часто сложное поведение частных методов может быть признаком того, что у класса есть более чем единственная ответственность. Поэтому, пожалуйста, сначала пересмотрите свой дизайн.
Дидье А.
Ага, но что такое «рабочий код»? Тестирование частного метода ничего не говорит о том, правильно ли ведет себя ваш объект. Это основная причина, по которой мы тестируем только общедоступные методы. Только общедоступные методы демонстрируют поведение, о котором заботится пользователь фрагмента кода.
Sammi
1
«Рабочий код» - это код, который работает. Если в вашем частном (или квази-частном) методе есть ошибка, которая не обнаруживается тестами для ваших общедоступных методов, значит, что-то не так. Возможно, ваш дизайн неправильный, достаточно справедливо: я согласен, что лучшее решение - это тесты, которые вызывают общедоступные методы. Но это не всегда возможно, особенно если вы добавляете или исправляете устаревший код. (Я говорю по опыту в проекте с 1 миллионом строк кода.) Протестированный код всегда лучше непроверенного, точка. Даже если мы нарушим правила тестирования только общедоступных методов!
Чарльз Рот,
Бит (вверху) о том, что «тесты - это связывание кода ... предотвращение рефакторинга» неверен на 100%. В архитектурной метафоре испытания - это строительные леса, а не бетон. Все меняется, тесты меняются, меня выкидывают, пишутся новые тесты. Я согласен, что хороший дизайн сводит к минимуму перезапись тестов. Но изменения случаются даже с лучшими проектами.
Чарльз Рот,
35

Вы написали:

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

Пожалуйста, позвольте мне перефразировать это на языке BDD :

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

* Может быть актуальным Interface или просто доступным API класса, например: Ruby не имеет интерфейсов.

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

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

Надеюсь это поможет!

Lunivore
источник
1
Блестящий пост. Много проясняет.
Frostymarvelous
17

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

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

Форсварир
источник
13

Нет! Только тестовые интерфейсы.

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

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

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

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

FGM
источник
9

Есть две причины для написания тестов:

  1. Утверждение ожидаемого поведения
  2. Предотвращение регресса поведения

Подход к (1) утверждению ожидаемого поведения:

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

  • Работает ли то, что я только что написал?
  • Действительно ли этот цикл заканчивается?
  • Это зацикливается в том порядке, в котором я думаю?
  • Будет ли это работать для нулевого ввода?

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

Это хорошая идея - делать это там, где вы считаете необходимым. Любой код, который немного сложен для понимания или нетривиален. От этого может выиграть даже тривиальный код. Все дело в вашей уверенности. Как часто это делать и как далеко идти, будет зависеть от вашего собственного удовлетворения. Остановитесь, когда сможете уверенно ответить «да» на вопрос: «Вы уверены, что это работает?»

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

Подход (2) Предотвращение регресса поведения:

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

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

Общий механизм для этого называется: кодирование до границы. Создавая границы между частями кода, вы защищаете все внутри границы от вещей за ее пределами. Границы становятся точкой взаимодействия и контрактом, по которому взаимодействуют вещи.

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

Это ваш типичный модульный тест, о котором все говорят, когда упоминают TDD или BDD. Дело в том, чтобы ужесточить границы и защитить их от изменений. Вы не хотите тестировать частные методы для этого, потому что частный метод не является границей. Защищенные методы - это ограниченная граница, и я бы защищал их. Они не открыты миру, но все же подвержены воздействию других отсеков или «Единиц».

Что с этим делать?

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

Да и нет.

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

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

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

Дидье А.
источник
1
Вы говорите так, будто если вы явно не тестируете частные методы изолированно, они не покрываются вашими тестами, и вы не можете доверять им работу. Я утверждаю, что это просто неправильно. Частный метод (или любой путь кода в нем), который нельзя проверить с помощью общедоступного метода, является мертвым кодом и должен быть удален. Весь смысл TDD заключается в том, чтобы получить полное покрытие только путем тестирования общедоступных методов, потому что вы пишете 0 LoC, которые не существуют, чтобы пройти тест. Тестирование изолированного частного метода служит ТОЛЬКО для усложнения рефакторинга, что является полной противоположностью (одной из) целей TDD.
Сара
@kai Я прямо заявляю, что вам не следует иметь автоматические тесты для частных методов, но иногда полезно иметь изолированные тесты, которые помогут вам с реализацией. Эти тесты не должны быть частью вашего набора тестов или должны быть отключены по той самой причине, которую вы упомянули: рефакторинг. Вы сами решаете, когда вы предпочитаете программный тест для реализации частного метода или нет. Может ты не дочитал до конца мой ответ?
Дидье А.
вы заявляете, что «есть также веская причина для тестирования частных методов, чтобы убедиться, что наша реализация работает». Я не вижу оснований для этого в посте. нет ничего, что тест частного метода мог бы рассказать вам о рабочей реализации, чего не мог бы сказать тест открытого метода. Частный метод либо работает, либо нет. Если это не сработает, проверка одного или нескольких общедоступных методов завершится ошибкой или это мертвый и / или непроверенный код.
Сара,
@kai Вы упомянули: «или это мертвый и / или непроверенный код». Я говорю о непроверенном коде. Частный метод может скрыть множество ошибок, крайние случаи которых не выполняются из общедоступных методов. Представьте себе отключение на одну ошибку. Иногда инварианты общедоступных методов делают так, что этого никогда не произойдет. В таком случае я бы посчитал, что частный метод по-прежнему содержит ошибки и имеет некорректную реализацию, но его интеграция предотвращает обнаружение и обнаружение ошибки. В этом случае вам может потребоваться несколько тестов для проверки крайних случаев, чтобы вы могли быть уверены, что ваш метод не содержит ошибок.
Дидье А.
@kai Но поймите, что те тесты, о которых я говорю, не ваш набор тестов, это не TDD. Я говорю, что некоторые частные методы легче реализовать, если вы можете быстро запустить против них несколько тестов. В языках с REPL вам это не нужно. И вы можете попробовать продумать метод в уме, но я рекомендую компьютерные тесты вместо трудных для реализации частных методов. Я предлагаю впоследствии удалить тесты или оставить их отключенными или в специальном месте, которое не запускается в вашей сборке CI.
Дидье А.
4

Нет, вам не следует тестировать частные методы (как бы вы все равно не использовали что-то ужасное, например, отражение). С защищенными методами это немного менее очевидно, в C # вы можете сделать что-то защищенным внутренним, и я думаю, что это нормально, чтобы протестировать производные классы, которые реализуют всю свою функциональность с помощью методов шаблона шаблона.

Но в целом, если вы думаете, что ваши общедоступные методы делают слишком много, пора реорганизовать ваши классы в более атомарные классы, а затем протестировать эти классы.

сатнхак
источник
2

Я тоже согласен с ответом @kwbeam о том, что не тестирует частные методы. Тем не менее, я хотел бы выделить важный момент - защищенные методы ЯВЛЯЮТСЯ частью экспортируемого API класса и, следовательно, ДОЛЖНЫ быть протестированы.

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

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

Надеюсь, поможет!

Codematix
источник
2

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

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

Простое сокрытие метода для клиента (создание закрытого) не дает ему права не подвергаться аудиту. Следовательно, они могут быть протестированы общедоступными методами, как упоминалось ранее.

Теоман Шипахи
источник
1

Я согласен со всеми: ответ на ваш вопрос - «нет».

Действительно, вы совершенно правы в своем подходе и своих мыслях, особенно в отношении покрытия кода.

Я бы также добавил, что этот вопрос (и ответ «нет») также относится к общедоступным методам, которые вы можете ввести в классы.

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

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

Quamrana
источник
0

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

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

Аудрюс Мескаускас
источник