Я прочитал этот пост о том, как тестировать частные методы. Обычно я не тестирую их, потому что я всегда думал, что быстрее проверять только общедоступные методы, которые будут вызваны извне объекта. Вы проверяете частные методы? Должен ли я их всегда тестировать?
Должен ли я тестировать частные методы или только публичные?
Ответ 1
Я не unit test частные методы. Частный метод - это деталь реализации, которая должна быть скрыта для пользователей класса. Тестирование частных методов ломает инкапсуляцию.
Если я нахожу, что частный метод является огромным или сложным или достаточно важным, чтобы потребовать его собственные тесты, я просто помещаю его в другой класс и делаю его общедоступным (Объект метода). Затем я могу легко протестировать ранее открытый, но теперь общедоступный метод, который теперь живет в своем собственном классе.
Ответ 2
В чем цель тестирования?
Большинство ответов до сих пор говорят о том, что частные методы - это детали реализации, которые не имеют (или, по крайней мере, не должны), до тех пор, пока публичный интерфейс хорошо протестирован и работает. Это абсолютно правильно, если ваша единственная цель для тестирования - гарантировать, что открытый интерфейс работает.
Лично мое основное использование для тестов кода заключается в том, чтобы гарантировать, что будущие изменения кода не вызовут проблем и не помогут моим усилиям по отладке, если они это сделают. Я нахожу, что тестирование частных методов так же тщательно, как и открытый интерфейс (если не больше!) Способствует этой цели.
Рассмотрим: у вас есть открытый метод A, который вызывает частный метод B. A и B используют метод C. C изменяется (возможно, вами, возможно, поставщиком), заставляя A начинать с отказа от своих тестов. Не было бы также полезно проводить тесты для B, даже несмотря на то, что они частные, так что вы знаете, есть ли проблема в использовании C, B использования C или обоих?
Тестирование частных методов также повышает ценность в тех случаях, когда охват тестирования открытого интерфейса является неполным. Хотя это та ситуация, которую мы обычно хотим избежать, тестирование единицы эффективности зависит как от тестов, обнаруживающих ошибки, так и от связанных с ними затрат на разработку и обслуживание этих тестов. В некоторых случаях преимущества 100% -ного охвата тестированием могут быть оценены недостаточно, чтобы гарантировать затраты на эти тесты, создавая пробелы в охвате тестирования в публичном интерфейсе. В таких случаях хорошо ориентированный тест частного метода может быть очень эффективным дополнением к кодовой базе.
Ответ 3
Я склонен следовать советам Дейва Томаса и Энди Ханта в их книге "Прагматическое тестирование":
В общем, вы не хотите нарушать инкапсуляцию ради (или, как говорила мама, "не подвергайте своих рядовых!" ). Наиболее времени, вы должны иметь возможность протестировать класс, используя его общественные методы. Если есть значительная функциональность, которая скрыта за закрытым или защищенным доступом, это может быть предупреждающим знаком, что там другой класс там изо всех сил пытается выбраться.
Но иногда я не могу перестать проверять частные методы, потому что это дает мне ощущение уверенности в том, что я создаю полностью надежную программу.
Ответ 4
Я чувствую себя вынужденным проверять частные функции, так как я следую все более и более одной из наших последних рекомендаций по обеспечению качества в нашем проекте:
Не более 10 в циклическая сложность за функцию.
Теперь побочным эффектом применения этой политики является то, что многие из моих очень больших публичных функций делятся на многие более целенаправленные, лучше называемые частные функции.
Общественная функция все еще существует (конечно), но по существу сводится к тому, чтобы называть все эти частные "подфункции"
Это действительно классно, потому что теперь вызов гораздо проще читать (вместо ошибки в большой функции, у меня есть ошибка в под-функции с именем предыдущих функций в стоп-кадре, чтобы помочь я понял, как я туда попал)
Тем не менее, теперь проще просто выполнить тесное тестирование этих частных функций и оставить тестирование большой публичной функции на каком-то "интеграционном" тесте, где нужно рассмотреть сценарий.
Только мои 2 цента.
Ответ 5
Да Я тестирую частные функции, потому что, хотя они тестируются вашими общедоступными методами, в TDD (Test Driven Design) приятно тестировать самую маленькую часть приложения. Но частные функции недоступны, когда вы находитесь в своем классе тестовой единицы. Вот что мы делаем, чтобы проверить наши частные методы.
Почему у нас есть частные методы?
Частные функции в основном существуют в нашем классе, потому что мы хотим создать читаемый код в наших общедоступных методах. Мы не хотим, чтобы пользователь этого класса вызывал эти методы напрямую, но через наши общедоступные методы. Кроме того, мы не хотим изменять свое поведение при расширении класса (в случае защиты), поэтому оно является частным.
При кодировании мы используем тестовый дизайн (TDD). Это означает, что иногда мы натыкаемся на часть функциональности, которая является частной и хочет протестировать. Частные функции не проверяются в phpUnit, потому что мы не можем получить к ним доступ в классе Test (они являются частными).
Мы считаем, что это 3 решения:
1. Вы можете проверить своих рядовых через общедоступные методы
Преимущества
- Простая модульная проверка (нет необходимости "взломать" )
Недостатки
- Программисту необходимо понять общедоступный метод, в то время как он хочет только проверить частный метод.
- Вы не тестируете самую маленькую тестируемую часть приложения.
2. Если частный так важен, то, возможно, это кодекс, чтобы создать для него новый отдельный класс.
Преимущества
- Вы можете реорганизовать это на новый класс, потому что если это так, важно, другие классы могут также нуждаться в этом.
- Тестируемое устройство теперь является общедоступным методом, поэтому проверяемое
Недостатки
- Вы не хотите создавать класс, если он не нужен, и используется только класс, в котором метод поступает из
- Потенциальная потеря производительности из-за дополнительных накладных расходов
3. Измените модификатор доступа на (окончательный)
Преимущества
- Вы тестируете самую маленькую тестируемую часть приложения. когда используя окончательную защиту, функция не будет переопределяемой (просто как частный).
- Без потери производительности
- Дополнительные накладные расходы
Недостатки
- Вы меняете приватный доступ к защищенному, что означает, что он доступный им дети
- Вам все еще нужен класс 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 для проверки нашей прежней частной функции.
Ответ 6
Я не люблю тестировать закрытые функции по нескольким причинам. Они заключаются в следующем (это основные моменты для людей TLDR):
- Обычно, когда вы испытываете желание протестировать закрытый метод класса, это дизайнерский запах.
- Вы можете проверить их через публику интерфейс (который, как вы хотите, чтобы проверить их, потому что, как клиент будет звонить/использовать их). Вы можете получить ложное чувство безопасности, видя зеленый свет на всех проходящих тестах для вашего частного методы. Намного лучше/безопаснее тестировать крайние случаи в ваших личных функциях через открытый интерфейс.
- Вы рискуете серьезным дублированием тестов (тесты, которые выглядят/чувствуются очень похоже) путем тестирования частных методов. Это имеет важное значение последствия, когда изменяются требования, столько же тестов, чем необходимо сломать. Это также может поставить вас в положение, когда это трудно реорганизовать из-за вашего набора тестов... который является окончательным ирония, потому что тестовый набор поможет вам безопасно переделать и рефакторинг!
Я объясню каждый из них на конкретном примере. Оказывается, что 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
для выполнения задания. Естественно, вы ожидаете, что один тест сломается, но эта боль усиливается, когда вы тестируете частные функции.
Когда может быть уместным тестирование частных методов?
В программном обеспечении нет "одного размера для всех". Иногда это нормально (и на самом деле идеально) "нарушать правила". Я настоятельно рекомендую не тестировать закрытые функции, когда это возможно. Есть две основные ситуации, когда я думаю, что все в порядке:
Я много работал с унаследованными системами (именно поэтому я такой большой поклонник Майкла Фезерса), и я могу с уверенностью сказать, что иногда просто безопаснее всего протестировать приватную функциональность. Это может быть особенно полезно для получения "тестов характеристик" в базовой линии.
Вы спешите, и вам нужно сделать как можно быстрее здесь и сейчас. В конце концов, вы не хотите тестировать частные методы. Но я скажу, что обычно требуется некоторое время на рефакторинг для решения проблем проектирования. И иногда вы должны отправить через неделю. Это нормально: делайте все быстро и грязно и тестируйте приватные методы, используя инструмент поиска, если это то, что вы считаете самым быстрым и надежным способом выполнения работы. Но поймите, что то, что вы сделали, было неоптимальным в долгосрочной перспективе, и, пожалуйста, рассмотрите возможность вернуться к нему (или, если об этом забыли, но вы увидите это позже, исправьте это).
Возможно, есть и другие ситуации, когда это нормально. Если вы думаете, что все в порядке, и у вас есть хорошее оправдание, тогда сделайте это. Никто не останавливает вас. Просто будьте в курсе потенциальных затрат.
Оправдание TDD
Кроме того, мне действительно не нравятся люди, использующие TDD в качестве оправдания для тестирования частных методов. Я практикую TDD и не думаю, что TDD заставляет вас делать это. Вы можете сначала написать свой тест (для вашего открытого интерфейса), а затем написать код, удовлетворяющий этому интерфейсу. Иногда я пишу тест для общедоступного интерфейса, и я удовлетворяю его, написав также один или два небольших приватных метода (но я не тестирую приватные методы напрямую, но я знаю, что они работают, иначе мой публичный тест будет неудачным).). Если мне нужно протестировать крайние случаи этого закрытого метода, я напишу целую кучу тестов, которые будут проходить через мой открытый интерфейс. Если вы не можете понять, как добиться успеха в крайних случаях, это сильный знак того, что вам нужно реорганизовать небольшие компоненты, каждый со своими открытыми методами. Это признак того, что ваши частные функции делают слишком много и выходят за рамки класса.
Кроме того, иногда я нахожу, что пишу тест, который слишком укусит, чтобы жевать в данный момент, и поэтому я думаю: "э, я вернусь к этому тесту позже, когда у меня будет больше API для работы" (я закомментирую и буду держать это в голове). Именно здесь многие разработчики, которых я встречал, начнут писать тесты для своей частной функциональности, используя TDD в качестве козла отпущения. Они говорят: "О, ну, мне нужен какой-то другой тест, но для написания этого теста мне понадобятся эти закрытые методы. Поэтому, поскольку я не могу написать производственный код без написания теста, мне нужно написать тест". для частного метода. " Но то, что им действительно нужно сделать, - это рефакторинг на более мелкие и повторно используемые компоненты вместо того, чтобы добавлять/тестировать кучу приватных методов в их текущий класс.
Примечание:
Я недавно ответил на аналогичный вопрос о тестировании частных методов с помощью GoogleTest. В основном я изменил этот ответ, чтобы сделать его более независимым от языка.
Постскриптум Вот соответствующая лекция Майкла Фезерса о занятиях айсбергом и нащупывании инструментов: https://www.youtube.com/watch?v=4cVZvoFGJTU
Ответ 7
Я думаю, что лучше всего просто проверить открытый интерфейс объекта. С точки зрения внешнего мира, только поведение общественного интерфейса имеет значение, и это то, к чему должны быть направлены ваши модульные тесты.
После того, как у вас есть какие-то твердые модульные тесты, написанные для объекта, который вам не нужно возвращать, и изменить эти тесты только потому, что реализация за интерфейсом изменилась. В этой ситуации вы испортили согласованность тестирования вашего устройства.
Ответ 8
Если ваш частный метод не протестирован, вызвав ваши общедоступные методы, то что он делает? Я говорю не частное, не защищенное или знакомое.
Ответ 9
Если частный метод хорошо определен (т.е. имеет функцию, которая может быть проверена и не предназначена для изменения со временем), тогда да. Я проверяю все, что можно проверить, где это имеет смысл.
Например, библиотека шифрования может скрыть тот факт, что она выполняет блочное шифрование с помощью частного метода, который шифрует только 8 байтов за раз. Я бы написал для него unit test - он не предназначен для изменения, даже если он скрыт, и если он сломается (из-за будущих улучшений производительности, например), то я хочу знать, что это закрытая частная функция, а не только что одна из общественных функций сломалась.
Он ускоряет отладку позже.
-Adam
Ответ 10
Если вы разрабатываете тестовый тест (TDD), вы проверите свои частные методы.
Ответ 11
Я не эксперт в этой области, но модульное тестирование должно проверять поведение, а не реализацию. Частные методы - это строго часть реализации, поэтому IMHO не следует тестировать.
Ответ 12
Мы тестируем частные методы путем вывода, под которым я подразумеваю, что мы ожидаем, что общий охват класса будет не менее 95%, но наши тесты будут только публичными или внутренними. Чтобы получить покрытие, нам необходимо сделать несколько вызовов для публики/внутренних компонентов на основе различных сценариев, которые могут произойти. Это делает наши тесты более устремленными вокруг цели тестируемого кода.
Ответ Trumpi на сообщение, которое вы связали, является лучшим.
Ответ 13
Модульные тесты, которые, я считаю, предназначены для тестирования общедоступных методов. Ваши общедоступные методы используют ваши частные методы, поэтому косвенно они также проходят тестирование.
Ответ 14
Я некоторое время туша по этому вопросу, особенно пытаясь разобраться в TDD.
Я столкнулся с двумя сообщениями, которые, я думаю, достаточно подробно рассматривают эту проблему в случае TDD.
- Тестирование частных методов, TDD и Тестирование рефакторинга с проверкой
- Test-Driven Development Isnt Testing
В резюме:
-
При использовании методов разработки (проектирования), основанных на тестах, частные методы должны возникать только во время процесса повторного факторинга уже работающего и протестированного кода.
-
По самой сути процесса любой бит простой функциональности реализации, извлеченный из полностью проверенной функции, будет самотестирован (т.е. косвенное тестирование).
Мне кажется, что достаточно ясно, что в начале части кодирования большинство методов будут функциями более высокого уровня, потому что они инкапсулируют/описывают дизайн.
Следовательно, эти методы будут общедоступными, и их тестирование будет достаточно простым.
Частные методы придут позже, как только все будет работать хорошо, и мы будем факторировать ради удобочитаемости и чистоты.
Ответ 15
Как указано выше, "Если вы не тестируете свои личные методы, как вы знаете, что они не сломаются?"
Это серьезная проблема. Одна из главных моментов модульных тестов - узнать, где, когда и как что-то сломалось как можно скорее. Таким образом, снижение значительного объема разработки и усилий по обеспечению качества. Если все, что протестировано, является общественностью, тогда у вас нет честного охвата и разграничения внутренних компонентов этого класса.
Я нашел один из лучших способов сделать это - просто добавить тестовую ссылку в проект и поставить тесты в классе параллельно с частными методами. Поместите соответствующую логику сборки, чтобы тесты не встраивались в окончательный проект.
Тогда у вас есть все преимущества тестирования этих методов, и вы можете найти проблемы в секундах или минутах или часах.
Итак, да, unit test ваши частные методы.
Ответ 16
Вы не должны. Если ваши частные методы имеют достаточную сложность, которые необходимо протестировать, вы должны поместить их в другой класс. Держите высокий уровень сцепления, класс должен иметь только одну цель. Открытый класс должен быть достаточно.
Ответ 17
Если вы не тестируете свои частные методы, как вы знаете, что они не сломаются?
Ответ 18
Он явно зависит от языка. Раньше с С++ я объявлял класс тестирования классом друзей. К сожалению, это требует, чтобы ваш производственный код знал о классе тестирования.
Ответ 19
Я понимаю точку зрения, где частные методы рассматриваются как детали реализации, а затем не нужно тестировать. И я бы придерживался этого правила, если бы мы должны были развиваться вне объекта. Но мы, какие-то ограниченные разработчики, которые развиваются только за пределами объектов, вызывая только их публичные методы? Или мы также разрабатываем этот объект? Поскольку мы не обязаны программировать внешние объекты, нам, вероятно, придется называть эти частные методы новыми публичными, которые мы разрабатываем. Не было бы замечательно знать, что частный метод сопротивляется всем противоречиям?
Я знаю, что некоторые люди могут ответить, что если мы разрабатываем еще один публичный метод в этом объекте, тогда это нужно протестировать и что он (частный метод может продолжать жить без теста). Но это справедливо и для любых общедоступных методов объекта: при разработке веб-приложения все общедоступные методы объекта вызывают из методов контроллеров и, следовательно, могут рассматриваться как детали реализации контроллеров.
Итак, почему мы единично тестируем объекты? Потому что это действительно сложно, не сказать, что невозможно быть уверенным в том, что мы тестируем методы контроллеров с соответствующим вводом, который инициирует все ветки базового кода. Другими словами, чем выше мы находимся в стеке, тем труднее проверить все поведение. И так же для частных методов.
Для меня граница между частными и общественными методами - это психологический критерий, когда дело доходит до тестов. Критерии, которые важнее для меня:
- - метод, вызываемый более одного раза из разных мест?
- - метод, достаточно сложный для тестирования?
Ответ 20
Если я нахожу, что частный метод является огромным или сложным или достаточно важным, чтобы потребовать его собственные тесты, я просто помещаю его в другой класс и делаю его общедоступным (объект метода). Затем я могу легко протестировать ранее закрытый, но теперь общедоступный метод, который теперь живет в своем собственном классе.
Ответ 21
Публичное и частное не является полезным отличием того, что apis вызывать из ваших тестов, равно как и метод против класса. Большинство тестируемых модулей видны в одном контексте, но скрыты в других.
Важен охват и стоимость. Вам необходимо минимизировать затраты при достижении целей покрытия вашего проекта (линии, ветки, пути, блока, метода, класса, класса эквивалентности, варианта использования... независимо от того, что решит команда).
Поэтому используйте инструменты для обеспечения покрытия и разрабатывайте свои тесты так, чтобы они приносили наименьшие затраты (краткосрочные и долгосрочные).
Не делайте тесты дороже, чем необходимо. Если дешевле всего протестировать только публичные точки входа, сделайте это. Если это дешевле всего протестировать частные методы, сделайте это.
По мере того, как вы становитесь более опытными, вы будете лучше предсказывать, когда стоит проводить рефакторинг, чтобы избежать долгосрочных затрат на обслуживание тестов.
Ответ 22
Если метод достаточно значителен/достаточно сложный, я обычно делаю его "защищенным" и проверяю его. Некоторые методы будут закрыты и проверены неявно в рамках модульных тестов для общедоступных/защищенных методов.
Ответ 23
Я вижу, что многие люди находятся в одной линии мышления: тест на общественном уровне. но разве это не то, что делает наша команда QA? Они тестируют ввод и ожидаемый результат. Если в качестве разработчиков мы проверяем только публичные методы, мы просто переделываем работу QA и не добавляем какую-либо ценность "модульным тестированием".
Ответ 24
Одним из основных моментов является
Если мы проверяем правильность логики, а частный метод несет логику, мы должны проверить ее. Не так ли? Так почему мы собираемся пропустить это?
Написание тестов, основанных на видимости методов, является совершенно неактуальной идеей.
Наоборот
С другой стороны, главная проблема заключается в вызове частного метода вне исходного класса. Кроме того, существуют ограничения для насмешки частного метода в некоторых инструментах насмешки. (Пример: Мокито)
Хотя есть некоторые инструменты, такие как Power Mock, которые поддерживают это, это опасная операция. Причина в том, что для этого нужно взломать JVM.
Можно обойти эту проблему (если вы хотите написать контрольные примеры для частных методов)
Объявите эти частные методы как защищенные. Но это может быть не удобно для нескольких ситуаций.
Ответ 25
Ответ на вопрос "Должен ли я тестировать частные методы?" "иногда бывает". Обычно вы должны тестировать интерфейс вашего класса.
- Одна из причин заключается в том, что вам не требуется двойное покрытие для функции.
- Другая причина заключается в том, что если вы меняете частные методы, вам придется обновлять каждый тест для них, даже если интерфейс вашего объекта вообще не изменился.
Вот пример:
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
Это не должно запускаться внешним пользователем, но ваш класс инкапсуляции может быть тяжелым, чтобы повторять много логики через него снова и снова. В этом случае, возможно, вы предпочтете извлечь это в отдельный класс, дать этому классу интерфейс и протестировать его.
И, наконец, скажем, что ваш основной объект супер тяжелый, а метод довольно мал, и вам действительно нужно убедиться, что результат правильный. Вы думаете: "Я должен проверить этот частный метод!". У вас есть, может быть, вы можете сделать свой объект легче, передав некоторые тяжелые работы в качестве параметра инициализации? Затем вы можете передать что-то более легкое и проверить на это.
Ответ 26
Нет. Вам не следует проверять частные методы почему?, и, кроме того, популярная насмешливая среда, такая как Mockito, не обеспечивает поддержку для тестирования частных методов.
Ответ 27
Я никогда не понимал концепцию модульного тестирования, но теперь я знаю, какова его цель.
Unit тест не является полным тестом. Таким образом, это не замена для QA и ручного тестирования. Концепция TDD в этом аспекте неверна, поскольку вы не можете протестировать все, включая частные методы, но также и методы, которые используют ресурсы (особенно ресурсы, которые мы не контролируем). TDD базируется на всем своем качестве, это то, чего нельзя было достичь.
Юнит-тест - это скорее сводный тест. Вы помечаете произвольный пивот, и результат его должен остаться прежним.
Ответ 28
Речь идет не только о публичных или частных методах или функциях, но и о деталях реализации. Частные функции - это только один аспект деталей реализации.
В конце концов, юнит-тестирование - это подход белого тестирования. Например, тот, кто использует анализ покрытия для определения частей кода, которыми до сих пор пренебрегали при тестировании, углубляется в детали реализации.
А) Да, вы должны тестировать детали реализации:
Подумайте о функции сортировки, которая по соображениям производительности использует частную реализацию BubbleSort, если имеется до 10 элементов, и частную реализацию другого подхода сортировки (скажем, heapsort), если имеется более 10 элементов. Публичный API - это функция сортировки. Однако ваш набор тестов лучше использует знания о том, что на самом деле используются два алгоритма сортировки.
В этом примере, безусловно, вы можете выполнить тесты с открытым API. Это, однако, потребует наличия нескольких тестовых случаев, которые выполняют функцию сортировки с более чем 10 элементами, так что алгоритм heapsort достаточно хорошо протестирован. Наличие таких тестовых примеров само по себе указывает на то, что набор тестов связан с деталями реализации функции.
Если детали реализации функции сортировки изменятся, возможно, из-за того, что предел между двумя алгоритмами сортировки будет смещен, или что heapsort будет заменен на mergesort или что-то еще: существующие тесты будут продолжать работать. Тем не менее, их ценность сомнительна, и их, вероятно, необходимо переработать, чтобы лучше протестировать измененную функцию сортировки. Другими словами, будут предприняты усилия по обслуживанию, несмотря на то, что тесты проводились на общедоступном API.
Б) Как проверить детали реализации
Одна из причин, почему многие люди утверждают, что не следует тестировать частные функции или детали реализации, заключается в том, что детали реализации с большей вероятностью изменятся. Эта более высокая вероятность изменений, по крайней мере, является одной из причин скрытия деталей реализации за интерфейсами.
Теперь предположим, что реализация интерфейса содержит более крупные частные части, для которых могут быть предусмотрены отдельные тесты внутреннего интерфейса. Некоторые люди утверждают, что эти части не должны быть проверены в частном порядке, они должны быть превращены во что-то публичное. После публичного юнит-тестирования этот код будет в порядке.
Это интересно: хотя интерфейс был внутренним, он, скорее всего, изменится, что является деталью реализации. Взяв тот же интерфейс, сделав его общедоступным, произойдет волшебное преобразование, а именно превращение его в интерфейс, который с меньшей вероятностью изменится. Очевидно, что в этой аргументации есть некоторый недостаток.
Но, тем не менее, в этом есть некоторая истина: при тестировании деталей реализации, в частности с использованием внутренних интерфейсов, следует стремиться использовать интерфейсы, которые, вероятно, останутся стабильными. Однако вероятность того, что какой-либо интерфейс будет стабильным, не просто зависит от того, является ли он общедоступным или закрытым. В проектах из мира, в которых я работал в течение некоторого времени, публичные интерфейсы также достаточно часто меняются, и многие частные интерфейсы остаются неизменными целую вечность.
Тем не менее, это хорошее эмпирическое правило, чтобы использовать "переднюю дверь первым" (см. Http://xunitpatterns.com/Principles%20of%20Test%20Automation.html). Но имейте в виду, что он называется "сначала входная дверь", а не "только входная дверь".
C) Резюме
Проверьте также детали реализации. Предпочитают тестирование на стабильных интерфейсах (публичных или приватных). Если детали реализации меняются, также необходимо пересмотреть тесты общедоступного API. Превращение чего-то частного в публичное не может волшебным образом изменить его стабильность
Ответ 29
Да, вы должны проверить частные методы, где это возможно. Зачем? Чтобы избежать ненужного взрыва в пространстве состояний тестовых случаев, которые в конечном итоге просто неявно тестируют одни и те же частные функции повторно на одних и тех же входах. Давайте объясним почему на примере.
Рассмотрим следующий слегка надуманный пример. Предположим, что мы хотим публично представить функцию, которая принимает 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 комбинаций входных сценариев, о которых мы говорили, о которых мы заботились. Всего несколько счастливых путей.
Придуманный пример, конечно, но это было необходимо для наглядной демонстрации. И шаблон распространяется на реальное программное обеспечение. Частные функции, как правило, являются строительными блоками самого низкого уровня и, таким образом, часто объединяются для получения логики более высокого уровня. То есть на более высоких уровнях у нас больше повторений вещей более низкого уровня из-за различных комбинаций.
Ответ 30
Вы также можете сделать свой метод package-private, то есть по умолчанию, и вы сможете использовать его для модульного тестирования, если только он не требуется для приватности.