Стоит ли тестировать частные методы или только публичные? [закрыто]

348

Я прочитал этот пост о том, как тестировать частные методы. Я обычно не проверяю их, потому что всегда думал, что быстрее проверять только публичные методы, которые будут вызываться извне объекта. Вы тестируете частные методы? Я должен всегда проверять их?

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

Ответы:

328

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

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

Джоп
источник
88
Я не согласен. В идеале вы должны написать быстрый тест, прежде чем начинать кодировать функцию. Подумайте о типичном входе и о том, какой будет выход. Напишите тест (который не займет у вас больше нескольких секунд) и кодируйте, пока тест не будет выполнен правильно. Нет причин отказываться от этого стиля работы для частных методов.
Фрэнк
254
Сказать, что частные методы не нуждаются в тестировании, все равно, что сказать, что с машиной все в порядке, если она нормально ездит, и не имеет значения, что находится под капотом. Но разве не хотелось бы знать, что какой-то кабель внутри начинает ослабевать - даже если пользователь ничего не замечает? Конечно, вы можете сделать все публично, но какой в ​​этом смысл? Вы всегда будете хотеть некоторые частные методы.
Фрэнк
37
«Закрытый метод - это деталь реализации, которая должна быть скрыта для пользователей класса». но действительно ли тесты находятся на той же стороне интерфейса класса, что и "обычные" пользователи (во время выполнения)? ;)
mlvljr
34
Опасность вытаскивания всего, что вы хотите протестировать, в другой класс состоит в том, что вы можете получить дополнительные издержки, связанные с чрезмерным проектированием вашего продукта, и наличием миллиона повторно используемых компонентов, которые никогда не будут повторно использоваться.
occulus
44
Сравнивать кусок кода с автомобилем неправильно; Код не « портится » со временем, он вечен . Если ваше тестирование общедоступного интерфейса заходит так далеко, что вы обнаруживаете, что оно « выглядит нормально », тогда вашего тестирования общедоступного кода недостаточно. В этом случае отдельное тестирование частных методов не завершит общий тест, как бы вы ни старались. Сконцентрируйтесь на полном тестировании вашего общедоступного кода в целом, используя знания внутренней работы кода для создания правильных сценариев.
rustyx
293

Какова цель тестирования?

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

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

Обратите внимание: у вас есть открытый метод A, который вызывает закрытый метод B. A и B оба используют метод C. C изменяется (возможно, вами, возможно, поставщиком), в результате чего A начинает проваливать свои тесты. Разве не было бы полезно иметь тесты на B также, хотя он и частный, чтобы вы знали, заключается ли проблема в том, что A использует C, B использует C или оба?

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

Дейв Шерохман
источник
72
Проблема здесь в том, что эти «будущие изменения кода» неизменно означают рефакторинг внутренней работы некоторого класса. Это происходит так часто, что написание тестов создает барьер для рефакторинга.
Программист вне закона
40
Кроме того, если вы постоянно меняете свои модульные тесты, то вы теряете всю согласованность в своих тестах, и вы даже потенциально будете создавать ошибки в самих модульных тестах.
17 из 26
6
@ 17 Если тесты и реализация изменяются синхронно (как, кажется, и должно быть), проблем будет гораздо меньше.
mlvljr
11
@Sauronlord, причина, по которой вы тестируете приватные методы, заключается в том, что если вы тестируете только публичные методы, когда тест неtestDoSomething()testDoSomethingPrivate() пройден, мы не знаем напрямую, где находится основная причина сбоя. Это может быть либо либо, либо . Это делает тест менее ценным. , Вот еще несколько причин для тестирования частного stackoverflow.com/questions/34571 /
....
3
@Pacerier Существует также разница между тестированием вашего кода и непрерывным автоматизированным процессом тестирования. Очевидно, вы должны убедиться, что ваш приватный метод работает, но у вас не должно быть тестов, связывающих вас с приватным методом, потому что он не является частью варианта использования программного обеспечения.
Дидье А.
150

Я склонен следовать советам Дейва Томаса и Энди Ханта в их книге « Прагматическое модульное тестирование» :

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

Но иногда я не могу удержаться от тестирования частных методов, потому что это дает мне чувство уверенности в том, что я создаю полностью надежную программу.

Розеллин Уорралл
источник
9
Я бы порекомендовал отключить юнит-тесты, предназначенные для частных методов. Они являются связующим звеном кода и будут обременять будущую работу по рефакторингу или даже иногда мешать добавлению или модификации функций. Хорошо написать тест для них, когда вы их внедряете, чтобы вы могли автоматически утверждать, что ваша реализация работает, но не стоит сохранять тесты регрессионными.
Дидье А.
61

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

Не более 10 по цикломатической сложности на функцию.

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

Это на самом деле круто, потому что callstack теперь намного легче читать (вместо ошибки в большой функции, у меня есть ошибка в подподфункции с именем предыдущих функций в callstack, чтобы помочь мне понять «как я туда попал»)

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

Просто мои 2 цента.

VonC
источник
2
чтобы реагировать на @jop, я не чувствую необходимости экспортировать эти частные функции (созданные из-за деления большой слишком цикломатической сложной публичной функции) в другой класс. Мне нравится, когда они все еще тесно связаны с публичной функцией в одном классе. Но все-таки юнит-тестирование.
VonC
2
Мой опыт показывает, что эти частные методы - это просто служебный метод, который повторно используется этими открытыми методами. Иногда удобнее разделить исходный класс на два (или три) более сплоченных класса, делая эти частные методы общедоступными в своих собственных классах и, следовательно, тестируемыми.
Джоп
7
Нет, в моем случае, эти новые частные функции действительно являются частью более крупного алгоритма, представленного открытой функцией. Эта функция разделена на более мелкие части, которые являются не утилитой, а этапами более крупного процесса. Отсюда необходимость их модульного тестирования (а не модульного тестирования всего алгоритма сразу)
VonC
Для тех, кто интересуется цикломатической сложностью, я добавил вопрос на эту тему: stackoverflow.com/questions/105852/…
VonC
К сожалению, URL вопроса изменился из-за опечатки в заголовке! stackoverflow.com/questions/105852/…
VonC
51

Да, я тестирую частные функции, потому что, хотя они тестируются вашими общедоступными методами, в TDD (Test Driven Design) было бы неплохо протестировать наименьшую часть приложения. Но частные функции недоступны, когда вы находитесь в классе тестового модуля. Вот что мы делаем, чтобы проверить наши частные методы.

Почему у нас есть частные методы?

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

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

Мы думаем, что есть 3 решения:

1. Вы можете проверить свои ряды с помощью публичных методов

преимущества

  • Простое юнит-тестирование (не требуется взлом)

Недостатки

  • Программист должен понимать публичный метод, в то время как он хочет только протестировать приватный метод
  • Вы не тестируете самую маленькую тестируемую часть приложения

2. Если приватность так важна, то, возможно, это кодовая ячейка, чтобы создать для нее новый отдельный класс

преимущества

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

Недостатки

  • Вы не хотите создавать класс, если он не нужен, и используется только тем классом, откуда приходит метод
  • Потенциальная потеря производительности из-за дополнительных накладных расходов

3. Измените модификатор доступа на (окончательный) защищенный

преимущества

  • Вы тестируете самую маленькую тестируемую часть приложения. При использовании final protected функция не будет переопределена (как приватная)
  • Без потери производительности
  • Никаких дополнительных накладных расходов

Недостатки

  • Вы изменяете частный доступ к защищенному, что означает, что он доступен для детей
  • Вам все еще нужен класс Mock в вашем тестовом классе, чтобы использовать его

пример

class Detective {
  public function investigate() {}
  private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
  public function investigate() {}
  final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {

  public test_sleepWithSuspect($suspect) 
  {
    //this is now accessible, but still not overridable!
    $this->sleepWithSuspect($suspect);
  }
}

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

eddy147
источник
eddy147, мне очень нравится концепция тестирования защищенных методов с помощью макетов. Спасибо!!!!
Теодор Р. Смит
15
Я просто хочу отметить, что в первоначальном описании TDD, при модульном тестировании, модуль - это класс , а не метод / функция. Поэтому, когда вы упоминаете «тестирование наименьшей части приложения», неправильно называть наименьшую тестируемую часть методом. Если вы используете эту логику, вы могли бы также говорить одну строку кода вместо целого блока кода.
Мэтт Куигли
@Matt Единица работы может указывать на класс, но также и на один метод.
eddy147
4
@ eddy147 Идет модульное тестирование Test Driven Development, где юнит был определен как класс. Как и в случае с Internets, семантика расширилась и теперь означает много вещей (т.е. спросите 2 человек, в чем разница между модульным и интеграционным тестированием, и вы получите 7 ответов). TDD был задуман как способ написания программного обеспечения с принципами SOLID, включая единую ответственность, когда класс несет единую ответственность и не должен иметь высокую циклическую сложность. В TDD вы пишете свой класс и тестируете вместе, оба модуля. Закрытые методы инкапсулированы и не имеют соответствующего модульного теста.
Мэтт Куигли
«Когда мы кодируем, мы используем тестируемый дизайн (TDD). Это означает, что иногда мы натыкаемся на частичную функциональность, которая нужна для тестирования». Я категорически не согласен с этим утверждением, пожалуйста, см. Мой ответ ниже для более подробной информации. TDD не означает, что вы вынуждены тестировать частные методы. Вы можете проверить частные методы: и это ваш выбор, но не TDD заставляет вас делать такие вещи.
Мэтт Мессерсмит
41

Я не люблю тестировать закрытые функции по нескольким причинам. Они заключаются в следующем (это основные моменты для людей TLDR):

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

Я объясню каждый из них на конкретном примере. Оказывается, что 2) и 3) несколько запутанно связаны, поэтому их пример похож, хотя я рассматриваю их как отдельные причины, по которым вам не следует тестировать частные методы.

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

Я также расскажу, почему TDD не является оправданием для тестирования частных методов в самом конце.

Рефакторинг вашего выхода из плохого дизайна

Один из самых распространенных (анти) паттернов, который я вижу, - это то, что Майкл Фезерс называет классом «Айсберг» (если вы не знаете, кто такой Майкл Фезерс, иди купите / прочитайте его книгу «Эффективная работа с устаревшим кодом»). человек, которого стоит знать, если вы профессиональный инженер / разработчик программного обеспечения). Существуют и другие (анти) паттерны, которые приводят к возникновению этой проблемы, но на данный момент это самая распространенная проблема, с которой я столкнулся. У классов "Айсберг" есть один публичный метод, а остальные - частные (вот почему заманчиво тестировать приватные методы). Он называется классом «Айсберг», потому что обычно выявляется одинокий публичный метод, но остальная часть функций скрыта под водой в виде частных методов.

Оценщик правил

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

TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    re = RuleEvaluator(input_string);

    ASSERT re.GetNextToken() IS "1";
    ASSERT re.GetNextToken() IS "2";
    ASSERT re.GetNextToken() IS "test";
    ASSERT re.GetNextToken() IS "bar";
    ASSERT re.HasMoreTokens() IS FALSE;
}

Ну, это на самом деле выглядит довольно мило. Мы хотели бы убедиться, что мы поддерживаем это поведение при внесении изменений. Но GetNextToken()это частная функция! Поэтому мы не можем протестировать его таким образом, потому что он даже не будет компилироваться (при условии, что мы используем какой-то язык, который на самом деле реализует public / private, в отличие от некоторых языков сценариев, таких как Python). Но как насчет изменения RuleEvaluatorкласса в соответствии с принципом единой ответственности (принцип единой ответственности)? Например, у нас, похоже, есть парсер, токенизатор и оценщик, объединенные в один класс. Не лучше ли разделить эти обязанности? Вдобавок Tokenizerко всему , если вы создадите класс, то это будут открытые методы HasMoreTokens()и GetNextTokens(). RuleEvaluatorКласс может иметьTokenizerобъект как член. Теперь мы можем сохранить тот же тест, что и выше, за исключением того, что мы тестируем Tokenizerкласс вместо RuleEvaluatorкласса.

Вот как это может выглядеть в UML:

Оценщик правил переработан

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

Тест выглядел бы чрезвычайно похожим, за исключением того, что он на самом деле компилируется на этот раз, так как GetNextToken()метод теперь открыт для Tokenizerкласса:

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS FALSE;
}

Тестирование приватных компонентов через публичный интерфейс и предотвращение дублирования тестов

Даже если вы не думаете, что можете разбить свою проблему на меньшее количество модульных компонентов (что вы можете сделать в 95% случаев, если просто попытаетесь это сделать), вы можете просто протестировать частные функции через открытый интерфейс. Часто частные члены не стоит тестировать, потому что они будут протестированы через открытый интерфейс. Часто я вижу тесты, которые выглядят очень похоже, но тестируют две разные функции / методы. В конечном итоге происходит то, что когда требования меняются (а они всегда меняются), у вас теперь есть 2 неработающих теста вместо 1. И если вы действительно проверили все свои частные методы, у вас может быть больше как 10 неработающих тестов вместо 1. Короче говоря , тестирование частных функций (с помощьюFRIEND_TESTили делая их публично или с использованием отражения) , которые в противном случае можно было бы проверить через публичный интерфейс может вызвать тест дублирования . Вы действительно не хотите этого, потому что ничто не повредит больше, чем ваш набор тестов, замедляющий вас. Это должно сократить время разработки и снизить затраты на обслуживание! Если вы тестируете частные методы, которые в противном случае тестируются через открытый интерфейс, набор тестов вполне может сделать обратное и активно увеличить затраты на обслуживание и увеличить время разработки. Когда вы делаете приватную функцию общедоступной, или если вы используете что-то вроде FRIEND_TESTи / или отражения, вы, как правило, в конечном итоге пожалеете об этом в долгосрочной перспективе.

Рассмотрим следующую возможную реализацию Tokenizerкласса:

введите описание изображения здесь

Допустим, SplitUpByDelimiter()он отвечает за возврат массива, так что каждый элемент массива является токеном. Кроме того, давайте просто скажем, что GetNextToken()это просто итератор для этого вектора. Итак, ваш публичный тест может выглядеть так:

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS false;
}

Давайте представим, что у нас есть то, что Майкл Фезер называет инструментом поиска . Это инструмент, который позволяет вам прикоснуться к частным частям других людей. Пример FRIEND_TESTиз GoogleTest, или рефлексия, если язык поддерживает это.

TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);
    result_array = tokenizer.SplitUpByDelimiter(" ");

    ASSERT result.size() IS 4;
    ASSERT result[0] IS "1";
    ASSERT result[1] IS "2";
    ASSERT result[2] IS "test";
    ASSERT result[3] IS "bar";
}

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

Когда может быть уместным тестирование частных методов?

В программном обеспечении нет «одного размера для всех». Иногда нормально (и на самом деле идеально) «нарушать правила». Я настоятельно рекомендую не тестировать закрытые функции, когда это возможно. Есть две основные ситуации, когда я думаю, что все в порядке:

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

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

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

Оправдание TDD

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

Кроме того, иногда я нахожу, что пишу тест, который слишком укусит, чтобы жевать в данный момент, и поэтому я думаю: «э, я вернусь к этому тесту позже, когда у меня будет больше API для работы» (я закомментирую и буду держать это в голове). Именно здесь многие разработчики, которых я встречал, начнут писать тесты для своей частной функциональности, используя TDD в качестве козла отпущения. Они говорят: «О, ну, мне нужен какой-то другой тест, но для написания этого теста мне понадобятся эти частные методы. Поэтому, поскольку я не могу написать производственный код без написания теста, мне нужно написать тест». для частного метода. " Но то, что им действительно нужно сделать, - это рефакторинг на более мелкие и повторно используемые компоненты вместо того, чтобы добавлять / тестировать кучу частных методов в их текущий класс.

Примечание:

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

PS Вот соответствующая лекция Майкла Фезерса об уроках айсберга и инструментах поиска: https://www.youtube.com/watch?v=4cVZvoFGJTU

Мэтт Мессерсмит
источник
Проблема, которую я имею с перечислением «вы можете потенциально использовать эти классы в других частях вашей системы» в качестве преимущества, заключается в том, что иногда я отмечаю функцию как частную, потому что я не хочу, чтобы она использовалась другими частями система. Это специфичная для языка проблема: в идеале это было бы приватно для «модуля», но если язык не поддерживает это (например, PHP), мой класс представляет модуль, а не модуль: закрытые методы - это код многократного использования с их собственными контрактами, но должны быть повторно использованы только в этом классе.
IMSoP
Я понимаю, что вы говорите, но мне нравится, как сообщество Python решает эту проблему. Если вы называете «закрытого» члена с ведущим _, это означает «эй, это« частный ». Вы можете использовать его, но полное раскрытие, он не предназначен для повторного использования, и вы должны использовать его, только если вы действительно знаю, что ты делаешь ". Вы можете использовать тот же подход на любом языке: сделать этих участников публичными, но пометить их ведущими _. Или, может быть, эти функции действительно должны быть закрытыми и просто протестированы через общедоступный интерфейс (подробности см. В ответе). Это в каждом конкретном случае, нет общего правила
Мэтт Мессерсмит
26

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

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

17 из 26
источник
21

Если ваш приватный метод не проверяется путем вызова ваших публичных методов, то что он делает? Я говорю приват, не защищенный или друг.

chrissie1
источник
3
Спасибо. Это удивительно недооцененный комментарий, особенно актуальный, даже спустя почти 8 лет после его написания.
Sauronlord
1
С тем же рассуждением можно утверждать, что тестирование программного обеспечения осуществляется только из пользовательского интерфейса (тестирование на уровне системы), поскольку каким-то образом каждая функция в программном обеспечении будет каким-то образом выполняться оттуда.
Дирк Херрманн
18

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

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

Это ускоряет отладку позже.

-Адам

Адам Дэвис
источник
1
В таком случае, не имеет ли смысл переместить этот закрытый метод в другой класс, а затем просто сделать его публичным или публичным статическим?
Outlaw Programmer
+1 Если вы не тестируете свои закрытые функции-члены, и ваш тест открытого интерфейса терпит неудачу, все, что вы получите, - это результат, который эквивалентен чему-то сломанному, не зная, что это такое.
Olumide
12

Если вы разрабатываете тест-драйв (TDD), вы будете тестировать свои частные методы.

Джадер Диас
источник
2
Вы извлечете частные методы после рефакторинга agiletips.blogspot.com/2008/11/…
Джош Джонсон,
4
Это не так, вы тестируете ваши публичные методы, и как только тесты пройдут, вы извлекаете код из ваших публичных методов в приватные методы во время этапа «очистки». Тестирование частных методов - плохая идея, потому что это усложняет изменение способа реализации (что, если когда-нибудь вы захотите изменить то, как вы что-то делаете, вы сможете изменить его и запустить все свои тесты, и если ваш новый способ выполнения все правильно, они должны пройти, я бы не хотел менять для этого все свои частные тесты).
Тессеракт
1
@Tesseract, если бы я мог поднять ваш комментарий более чем один раз, я бы. «... вы должны быть в состоянии изменить его и запустить все свои тесты, и если ваш новый способ сделать все правильно, они должны пройти». Это одно из главных преимуществ модульных тестов. Они позволяют вам рефакторинг с уверенностью. Вы можете полностью изменить внутреннюю приватную работу вашего класса и (не переписывая все свои модульные тесты) быть уверенными, что вы ничего не сломали, потому что все ваши (существующие) модульные тесты (в вашем общедоступном интерфейсе) все еще проходят.
Ли
Не согласен, смотрите мой ответ ниже
Мэтт Мессерсмит
11

Я не эксперт в этой области, но модульное тестирование должно проверять поведение, а не реализацию. Частные методы являются строго частью реализации, поэтому ИМХО не следует тестировать.

maxbog
источник
Где тогда тестируется реализация? Если некоторые функции используют кеширование, то это детали реализации и кеширование не проверяется?
Дирк Херрманн
11

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

Ответ Трампи на пост, на который вы ссылаетесь, является лучшим.

Том Карр
источник
9

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

scubabbl
источник
7

Я некоторое время размышлял над этой проблемой, особенно когда попробовал свои силы в TDD.

Я натолкнулся на два сообщения, которые, я думаю, достаточно подробно решают эту проблему в случае TDD.

  1. Тестирование частных методов, TDD и тест-рефакторинг
  2. Разработка через тестирование - это не тестирование

В итоге:

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

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

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

Поэтому эти методы будут общедоступными, и их тестирование будет достаточно простым.

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

dkinzer
источник
6

Как указано выше: «Если вы не тестируете свои личные методы, откуда вы знаете, что они не сломаются?»

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

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

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

Итак, в заключение, да, юнит-тестирование ваших частных методов.

Adron
источник
2
Я не согласен. «Если вы не тестируете свои личные методы, как вы узнаете, что они не сломаются?» Я знаю это, потому что, если мои частные методы будут повреждены, то тесты, которые проверяют мои публичные методы, основанные на этих закрытых методах, не пройдут. Я не хочу менять свои тесты каждый раз, когда меняю свое мнение о том, как реализовать публичные методы. Я также думаю, что основной интерес модульных тестов заключается не в том, чтобы точно знать, какая строка кода неисправна, скорее, это позволяет вам быть более или менее уверенным, что вы ничего не сломали при внесении изменений (в частные методы).
Тессеракт
6

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

fernandezdavid7
источник
3

Если вы не тестируете свои личные методы, как вы узнаете, что они не сломаются?

Билли Джо
источник
19
Путем написания тестов ваших публичных методов.
scubabbl
3
Эти частные методы предположительно вызываются открытыми методами класса. Так что просто протестируйте публичные методы, которые вызывают приватные методы.
Джоп
1
Если ваши публичные методы работают должным образом, то очевидно, что частные методы, к которым они обращаются, работают правильно.
17 из 26
Если тесты ваших общедоступных методов не пройдены, вы сразу узнаете, что что-то не так на более низком уровне вашего объекта / компонента / и т. Д.
Роб
3
Однако очень приятно знать, что сломалась внутренняя функция, а не только внешние функции (или наоборот, что внутренние функции хороши, и вы можете сосредоточиться на внешней).
Адам Дэвис
2

Это очевидно зависит от языка. В прошлом с c ++ я объявил класс тестирования своим классом. К сожалению, для этого требуется, чтобы ваш производственный код знал о классе тестирования.

Dvorak
источник
5
Ключевое слово друга делает меня грустным.
Роб
Это не проблема, если класс тестирования реализован в другом проекте. Важно то, что производственный код не ссылается на класс тестирования.
Olumide
2

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

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

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

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

  • метод вызывается более одного раза из разных мест?
  • метод достаточно сложен, чтобы требовать испытаний?
Оливье Пишон
источник
1

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

Энди
источник
1

Я никогда не понимал концепцию модульного тестирования, но теперь я знаю, какова его цель.

Модульный тест не является полным тестом . Таким образом, это не замена для QA и ручного тестирования. Концепция TDD в этом аспекте неверна, поскольку вы не можете протестировать все, включая частные методы, но также и методы, которые используют ресурсы (особенно ресурсы, которые мы не контролируем). TDD базируется на всем своем качестве, это то, чего нельзя было достичь.

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

Магеллана
источник
1

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

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

Поэтому используйте инструменты для обеспечения покрытия и разрабатывайте свои тесты так, чтобы они приносили наименьшие затраты (краткосрочные и долгосрочные )

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

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

tkruse
источник
0

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


источник
1
@VisibleForTesting это аннотация для этого. Я бы не стал расслаблять инкапсуляцию для тестирования, лучше использую dp4j.com
simpatico
0

Я вижу, что многие люди придерживаются единого мнения: тестирование на общественном уровне. но разве это не то, что делает наша команда QA? Они проверяют ввод и ожидаемый результат. Если как разработчики мы тестируем только общедоступные методы, то мы просто переделываем работу QA и не добавляем никакой ценности путем «модульного тестирования».

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

Ответ на вопрос «Должен ли я проверить частные методы?» это "....... иногда". Обычно вы должны проверять интерфейс ваших классов.

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

Вот пример:

class Thing
  def some_string
    one + two
  end

  private 

  def one
    'aaaa'
  end

  def two
    'bbbb'
  end

end


class RefactoredThing
def some_string
    one + one_a + two + two_b
  end

  private 

  def one
    'aa'
  end

  def one_a
    'aa'
  end

  def two
    'bb'
  end

  def two_b
    'bb'
  end
end

В RefactoredThingвас теперь есть 5 тестов, 2 из которых вы должны были обновить рефакторинга, но его функциональность объекта действительно не изменилось. Итак, допустим, что все более сложно, и у вас есть метод, который определяет порядок вывода, такой как:

def some_string_positioner
  if some case
  elsif other case
  elsif other case
  elsif other case
  else one more case
  end
end

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

И, наконец, допустим, что ваш основной объект очень тяжелый, а метод довольно маленький, и вам действительно нужно убедиться, что вывод правильный. Вы думаете: «Я должен проверить этот частный метод!». Возможно, вы можете сделать свой объект легче, передавая некоторые тяжелые работы в качестве параметра инициализации? Тогда вы можете передать что-то более легкое и протестировать против этого.

unflores
источник
0

Нет. Вы не должны проверять частные методы, почему? и, кроме того, популярная среда моделирования, такая как Mockito, не обеспечивает поддержку тестирования частных методов.

cammando
источник
0

Одним из основных моментов является

Если мы проверяем правильность логики, а частный метод несет логику, мы должны проверить ее. Не так ли? Так почему мы собираемся пропустить это?

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

Наоборот

С другой стороны, главная проблема заключается в вызове частного метода вне исходного класса. Кроме того, существуют ограничения для насмешки частного метода в некоторых инструментах насмешек. (Пример: Мокито )

Хотя есть некоторые инструменты, такие как Power Mock, которые поддерживают это, это опасная операция. Причина в том, что для этого нужно взломать JVM.

Можно обойти эту проблему (если вы хотите написать контрольные примеры для частных методов)

Объявите эти частные методы как защищенные . Но это может быть не удобно для нескольких ситуаций.

Supun Wijerathne
источник
0

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

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

А) Да, вы должны тестировать детали реализации:

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

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

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

Б) Как проверить детали реализации

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

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

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

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

Тем не менее, рекомендуется использовать «переднюю дверь первым» (см. Http://xunitpatterns.com/Principles%20of%20Test%20Automation.html ). Но имейте в виду, что он называется «сначала входная дверь», а не «только входная дверь».

C) Резюме

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

Дирк Херрманн
источник
0

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

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

public bool allPrime(int a, int b, int c)
{
  return andAll(isPrime(a), isPrime(b), isPrime(c))
}

private bool andAll(bool... boolArray)
{
  foreach (bool b in boolArray)
  {
    if(b == false) return false;
  }
  return true;
}

private bool isPrime(int x){
  //Implementation to go here. Sorry if you were expecting a prime sieve.
}

Теперь, если бы мы придерживались строгого подхода, согласно которому должны проверяться только публичные функции, нам разрешалось бы только проверять, allPrimeа не isPrimeили andAll.

В качестве тестера, мы могли бы быть заинтересованы в пять возможностей для каждого аргумента: < 0, = 0, = 1, prime > 1, not prime > 1. Но чтобы быть тщательным, мы должны также увидеть, как каждая комбинация аргументов играет вместе. Так что это 5*5*5= 125 тестовых случаев, нам нужно было бы тщательно протестировать эту функцию, согласно нашей интуиции.

С другой стороны, если бы нам было разрешено тестировать частные функции, мы могли бы охватить как можно больше с меньшим количеством тестовых случаев. Нам понадобится всего 5 тестовых случаев, чтобы протестировать isPrimeдо того же уровня, что и наша предыдущая интуиция И в соответствии с гипотезой о малом объеме, предложенной Дэниелом Джексоном, нам нужно было бы протестировать andAllфункцию только до небольшой длины, например 3 или 4. Это будет не более 16 тестов. Итого 21 тест. Вместо 125. Конечно, мы, вероятно, хотели бы провести несколько тестов allPrime, но мы бы не чувствовали себя настолько обязанными, чтобы охватить все 125 комбинаций входных сценариев, о которых мы говорили, о которых мы заботились. Всего несколько счастливых путей.

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

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

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

Yogesh
источник