it-swarm.com.ru

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

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

312
Patrick Desjardins

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

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

300
jop

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

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

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

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

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

279
Dave Sherohman

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

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

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

140
Rosellyne Worrall

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

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

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

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

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

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

58
VonC

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

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

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

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

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

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

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

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

Недостатки

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

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

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

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

Недостатки

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

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

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

  • Вы тестируете самую маленькую тестируемую часть приложения. При использовании 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 для проверки нашей прежней частной функции.

50
eddy147

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

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

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

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

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

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

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

Rule Evaluator

Например, вы можете проверить 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:

Rule Evaluator Refactored

Обратите внимание, что этот новый дизайн увеличивает модульность, так что вы можете потенциально использовать эти классы в других частях вашей системы (прежде чем вы не смогли, частные методы не могут быть повторно использованы по определению). Это является основным преимуществом разрушения 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:

enter image description here

Допустим, что 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 вам сделать это. Вы можете сначала написать свой тест (для вашего открытого интерфейса), а затем написать код, удовлетворяющий этому интерфейсу. Иногда я пишу тест для общедоступного интерфейса, и я удовлетворяю его, написав также один или два небольших приватных метода (но я не тестирую приватные методы напрямую, но я знаю, что они работают, иначе мой публичный тест будет неудачным). ). Если мне нужно протестировать случаи Edge этого закрытого метода, я напишу целую кучу тестов, которые будут проходить через мой открытый интерфейс. Если вы не можете понять, как использовать Edge, это сильный знак того, что вам нужно реорганизовать небольшие компоненты, каждый со своими открытыми методами. Это признак того, что частные функции слишком много делают и выходят за рамки класса .

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

Замечания:

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

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

25
Matt Messersmith

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

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

24
17 of 26

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

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

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

-Адам

19
Adam Davis

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

17
chrissie1

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

12
Jader Dias

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

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

11
Tom Carr

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

10
maxbog

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

9
scubabbl

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

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

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

В заключение:

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

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

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

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

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

7
dkinzer

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

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

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

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

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

6
Adron

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

5
fernandezdavid7

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

3
Billy Jo

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

2
dvorak

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

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

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

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

  • метод вызывается более одного раза из разных мест?
  • метод достаточно сложен, чтобы требовать испытаний?
2
Olivier Pichon

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

1
Andy

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

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

Вот пример:

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

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

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

0
unflores

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

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

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

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

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

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

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

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

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

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

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

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

C) Резюме

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

0
Dirk Herrmann

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

0
Techiee

один главный момент

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

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

Обратно

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

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

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

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

0
Supun Wijerathne

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

0
akapulko2020

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

Рассмотрим следующий слегка надуманный пример. Предположим, что мы хотим публично представить функцию, которая принимает 3 целых числа и возвращает true тогда и только тогда, когда все эти 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 комбинации входных сценариев, о которых мы говорили, о которых мы заботились. Всего несколько счастливых путей.

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

0
Colm Bhandal

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

0
aemorales1

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

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

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

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

Личное против публичного не имеет значения.

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

0
tkruse

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

0
cammando

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

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

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

0
magallanes