Почему юнит-тестирование частных методов считается плохой практикой?

17

Контекст:

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

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

Проблема:

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

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

Вопрос:

Я забочусь о следовании передовой практике и хотел бы понять:

  • почему плохо использовать модульные тесты для закрытых / скрытых методов (каков риск)?
  • Каковы лучшие практики, когда общедоступные методы могут использовать низкоуровневую и / или сложную обработку?

Precisions:

  • это не как вопрос. Python не имеет истинной концепции конфиденциальности, и скрытые методы просто не перечислены, но могут использоваться, когда вы знаете их имя
  • Меня никогда не учили правилам и шаблонам программирования: мои последние уроки были с 80-х годов ... В основном я учил языки методом проб и неудач и ссылками в Интернете (Stack Exchange - мой любимый в течение многих лет)
Серж Баллеста
источник
2
Возможный дубликат тестирования частных методов как защищенных
Грег Бургхардт
2
ОП, где вы слышали или читали, что тестирование частных методов считалось «плохим»? Существуют разные способы модульного тестирования. См. Модульное тестирование, тестирование черного ящика и тестирование белого ящика .
Джон Ву,
@JohnWu: я знаю разницу между тестированием белого ящика и черного ящика. Но даже в тестировании «белого ящика» кажется, что необходимость тестировать частные методы является подсказкой для проблемы проектирования. Мой вопрос - попытка понять, каковы лучшие пути, когда я падаю туда ...
Серж Баллеста,
2
Опять же, где вы слышали или читали, что даже в тестировании «белого ящика» необходимость тестирования частных методов является намеком на проблему проектирования? Я хотел бы понять причины этого убеждения, прежде чем пытаться ответить.
Джон Ву
@SergeBallesta, другими словами, приведите несколько ссылок на те статьи, которые внушали вам, что тестирование частных методов - плохая практика. Тогда объясните нам, почему вы им верите.
Laiv

Ответы:

17

Пара причин:

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

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

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

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

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

У меня есть ответ, подробно описывающий это в SO, который я не буду повторять здесь: /programming/105007/should-i-test-private-methods-or-only-public-ones/ 47401015 # 47401015

Мэтт Мессерсмит
источник
4
Причина 1: туманность. Причина 2: Что делать, если ваш частный вспомогательный метод не должен быть частью общедоступного API? Причина 3: Нет, если вы правильно разработали свой класс. Ваш последний совет: зачем мне удалять совершенно хороший тест, который доказывает, что метод, который я написал, работает?
Роберт Харви
4
@RobertHarvey Причина 2: косвенный доступ через общедоступный API! = Участие в общедоступном API. Если ваша приватная функция не тестируется через публичный API, то, возможно, это мертвый код, и его следует удалить? Или ваш класс действительно является айсбергом (причина 1) и должен быть реорганизован.
Frax
5
@ RobertHarvey, если вы не можете протестировать приватную функцию через публичный API, то удалите ее, так как она не приносит никакой пользы.
Дэвид Арно
1
@RobertHarvey 1: запахи дизайна всегда несколько субъективны / туманны, так что обязательно. Но я перечислил несколько конкретных примеров анти-паттернов, и в моем SO-ответе есть больше деталей. 2: Частные методы не могут быть частью общедоступного API (по определению: они являются частными) ... поэтому я не думаю, что ваш вопрос имеет большой смысл. Я пытаюсь доказать, что если у вас есть что-то вроде bin_search(arr, item)(public) и bin_search(arr, item, low, hi)(private, есть много способов выполнить поиск по bin_search(arr, item)
бину
1
@RobertHarvey 3: Во-первых, я сказал, что риск , а не гарантия . Во-вторых, заявлять, что «это работает, если вы делаете это правильно», является самоисполняющимся. Например, «Вы можете написать операционную систему в одной функции, если вы делаете это правильно ». Это не ложь: но это также не полезно. О подсказке: вы удалили бы его, потому что если ваша реализация изменится (то есть вы захотите поменять частный импл), то ваш набор тестов будет мешать вам (у вас будет неудачный тест, где вы не должны).
Мэтт Мессерсмит
14

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

Пит
источник
Не уверен, почему ваш ответ был отклонен. Это коротко, к сути и получает ответ на 100% правильный.
Дэвид Арно
3
@DavidArno: Возможно, потому что тестирование частных методов не имеет большого отношения к гранулярности теста. Это связано с деталями реализации.
Роберт Харви
11

Написание модульных тестов для частных методов связывает ваши модульные тесты с деталями реализации.

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

Тем не менее, почему , возможно , вы хотите , чтобы писать тесты для частных методов?

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

В некоторых экосистемах, таких как C # и .NET, есть способы создания сред, подобных REPL (такие инструменты, как Linqpad, делают это), но их полезность ограничена, потому что у вас нет доступа к вашему проекту. Непосредственное окно в Visual Studio неудобно; у него все еще нет полного Intellisense, вы должны использовать в нем полностью определенные имена, и он запускает сборку каждый раз, когда вы его используете.

Роберт Харви
источник
4
@ user949300 Немного трудно спорить об этом, не впадая в истинную ошибку шотландца , но есть много плохо написанных тестов всех видов. С точки зрения модульного тестирования вы должны протестировать публичный контракт вашего метода, не зная внутренних деталей реализации. Не то чтобы утверждение, что определенная зависимость была названа X раз, всегда неверно: бывают ситуации, когда это имеет смысл. Вам просто нужно убедиться, что это та информация, которую вы действительно хотите передать в контракте с тестируемой единицей.
Винсент Савард
3
@DavidArno: [ пожимает плечами] Я занимаюсь этим уже некоторое время. Модульные тесты для частных методов всегда работали для меня, пока Microsoft не решила прекратить поддержку прокси-объектов в их тестовой среде. В результате ничего не взорвалось. Я никогда не пробивал дыру во вселенной, написав тест для частного метода.
Роберт Харви
2
@DavidArno: Почему я перестал бы использовать совершенно хорошую технику, которая дает мне выгоду, просто потому, что кто-то в интернете говорит, что это плохая идея без объяснения причин?
Роберт Харви
2
Основное преимущество, которое я получаю от модульных тестов, состоит в том, чтобы дать мне «сеть безопасности», которая позволяет мне переделывать свой код и быть уверенным, зная, что мои изменения не приводят к регрессиям. Для этого тестирование частных вспомогательных методов облегчает поиск таких регрессий. Когда я реорганизую частный вспомогательный метод и ввожу логическую ошибку, я ломаю тесты, специфичные для этого частного метода. Если бы мои модульные тесты были более общими и проверяли только интерфейс этой единицы кода, то проблема была бы гораздо более неясной.
Александр - Восстановить Монику
2
@ Frax Конечно, я мог, но по этой логике я должен отказаться от модульных тестов в пользу общесистемных интеграционных тестов. В конце концов, «в большинстве случаев вы должны иметь возможность изменять эти тесты для проверки того же поведения»
Александр - восстановите Монику
6

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

Это приводит к лучшему соблюдению принципа единой ответственности.

Роберт Анджейюк
источник
Это должен быть принятый ответ.
Джек Эйдли,
0

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

Может быть, лучше задать вопрос: «Что мне делать, если я хочу протестировать частные методы?» и ответ вроде бы очевиден: вы должны показывать их так, чтобы сделать возможным тестирование. Теперь, это не обязательно означает, что вы должны просто сделать метод публичным и все. Скорее всего, вы захотите сделать более высокую абстракцию; Перейдите в другую библиотеку или API, чтобы вы могли выполнять свои тесты в этой библиотеке, не раскрывая эту функциональность в своем основном API.

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

Хуан Карлос Эдуардо Ромайна Ас
источник
Я попытался ответить на этот вопрос в своем вопросе, сказав, что в Python нет истинной концепции конфиденциальности, и скрытые методы просто не перечислены, но их можно использовать, когда вы знаете их имя
Серж Баллеста,