Иногда частные функции - это просто еще не извлеченные внутренние единицы функциональности. Так почему бы не проверить их?

9

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

Демонстрировать:

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

Сначала я написал module_a. Теперь я хочу написать тесты для этого. Я хотел бы проверить «частную» функцию _private_func. Я не понимаю, почему я не написал бы тест для него, если позже я все равно мог бы реорганизовать его в свой собственный внутренний модуль, а затем написать тесты для него.


Предположим, у меня есть модуль со следующими функциями (это может быть также класс):

def public_func(a):
    b = _do_stuff(a)
    return _do_more_stuff(b)

_do_stuffи _do_more_stuffявляются «частными» функциями модуля.

Я понимаю идею, что мы должны проверять только открытый интерфейс, а не детали реализации. Однако вот в чем дело:

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

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

Авив Кон
источник
2
«Частные методы полезны для модульного тестирования ...» «... применение частного метода приносит мне полезное, надежное улучшение в модульных тестах. Напротив, ослабление ограничений доступа« для тестируемости »только дает мне неясный, трудный для понимания кусок тестового кода, который, кроме того, подвергается постоянному риску быть нарушенным любым незначительным рефакторингом; честно говоря, то, что я получаю, выглядит подозрительно, как технический долг "
комнат
3
"Должен ли я проверить частные функции?" Нет, никогда, никогда.
Дэвид Арно
2
@DavidArno Почему? Какая альтернатива для тестирования внутренних устройств? Только интеграционные тесты? Или сделать больше вещей публичными? (хотя по своему опыту я в основном тестирую публичные методы на внутренних классах, а не на частных)
CodesInChaos
1
Если достаточно важно, чтобы для него нужно было писать тесты, то он уже должен быть в отдельном модуле. Если это не так, то вы проверяете его поведение с помощью общедоступного API.
Винсент Савард

Ответы:

14

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

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

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

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

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

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

Почему должен ты? Зачем проверять то, что вы никогда не увидите? Потому что все меняется. Это может быть приватным сейчас, но позже публичным. Код вызова может измениться. Код, который явно отклоняет null, делает понятным правильное использование и ожидаемое состояние.

Конечно, ноль может быть в порядке. Это просто пример здесь. Но если вы ожидаете чего-то, полезно прояснить это ожидание.

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

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

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

candied_orange
источник
У меня есть подход, я хотел бы услышать ваше мнение: всякий раз, когда я нахожусь в ситуации, когда я хочу проверить частную функцию, я проверю, могу ли я проверить ее в достаточной степени с помощью одной из открытых функций. (включая все крайние случаи и т. д.). Если я могу, я не буду писать тест для этой функции специально, а только тестировать через тестирование открытых функций, которые ее используют. Однако, если я чувствую, что функция не может быть протестирована в достаточной степени через открытый интерфейс и заслуживает отдельного теста, я извлеку ее во внутренний модуль, где она общедоступна, и напишу тесты для нее. Что вы думаете?
Авив Кон
Я скажу, что я спрашиваю то же самое у других парней, которые ответили здесь :) Я хотел бы услышать мнение каждого.
Авив Кон
Опять же, я не скажу вам нет. Я обеспокоен тем, что вы ничего не сказали о том, чтобы следить за тем, как это влияет на удобство использования. Разница между публичным и частным не является структурной. Это использование. Если разница между общественным и частным является входной и задней дверью, то ваша работа заключается в том, чтобы построить сарай на заднем дворе. Хорошо. Пока люди не заблудятся там.
Candied_Orange
1
За это проголосовало «Если тестирование через открытый интерфейс не выполняет частную функцию в достаточной степени, частная функция пытается разрешить многое».
Крис Уэлш
7

Краткий ответ: нет

Более длинный ответ: да, но через общедоступный «API» вашего класса

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

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


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

richzilla
источник
Привет, спасибо за ваш ответ :) Пожалуйста, перечитайте вопрос, я отредактировал, чтобы уточнить.
Авив Кон
У меня есть подход, я хотел бы услышать ваше мнение: всякий раз, когда я нахожусь в ситуации, когда я хочу проверить частную функцию, я проверю, могу ли я проверить ее в достаточной степени с помощью одной из открытых функций. (включая все крайние случаи и т. д.). Если я могу, я не буду писать тест для этой функции специально, а только тестировать через тестирование открытых функций, которые ее используют. Однако, если я чувствую, что функция не может быть протестирована в достаточной степени через открытый интерфейс и заслуживает отдельного теста, я извлеку ее во внутренний модуль, где она общедоступна, и напишу тесты для нее. Что вы думаете?
Авив Кон
Я скажу, что я спрашиваю то же самое у других парней, которые ответили здесь :) Я хотел бы услышать мнение каждого.
Авив Кон
5

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

def public_func(a):
    b = _do_stuff(a)
    return _do_more_stuff(b)

Если у вас есть серия тестов, которые используют только public_func, то если вы переписываете его в:

def public_func(a):
    b = _do_all_the_new_stuff(a)
    return _do_all_the_old_stuff(b)

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

Это все хорошо: статический публичный API; хорошо инкапсулированные внутренние работы; и надежные тесты.

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

Это плохо: меняющийся API; открытые внутренние работы, тесно связанные с испытаниями; и хрупкие тесты, которые изменяются, как только вносятся изменения в реализацию.

Так что нет, не тестируйте частные функции. Когда-либо.

Дэвид Арно
источник
Привет Дэвид, спасибо за ваш ответ. Пожалуйста, перечитайте мой вопрос, я отредактировал, чтобы уточнить.
Авив Кон
Мех. Нет ничего плохого в тестировании внутренних функций. Лично я не пишу нетривиальную функцию любого вида, если не могу покрыть ее парой модульных тестов, поэтому запрет на тестирование внутренних функций в значительной степени лишит меня возможности писать внутренние функции, лишив меня очень полезной техники. API могут и должны меняться; когда они это сделают, вы должны изменить тесты. Рефакторинг внутренней функции (и ее тест) не нарушает тесты внешней функции; в этом весь смысл этих тестов. Плохой совет в целом.
Роберт Харви
На самом деле вы спорите, что у вас не должно быть частных функций.
Роберт Харви
1
@AvivCohn Они либо достаточно большие, чтобы проводить тестирование, и в этом случае они достаточно велики, чтобы получить собственный файл; или они настолько малы, что вам не нужно тестировать их отдельно. Так что это?
Довал
3
@RobertHarvey Нет, он заставляет аргумент «разбивать большие классы на слабосвязанные компоненты при необходимости». Если они действительно не являются публичными, это, вероятно, хороший вариант использования для частной видимости пакета.
Довал