Я только сейчас изучаю TDD. Насколько я понимаю, частные методы не поддаются проверке и не должны беспокоиться, потому что общедоступный API предоставит достаточно информации для проверки целостности объекта.
Я понял ООП некоторое время. Насколько я понимаю, частные методы делают объекты более инкапсулированными, таким образом, более устойчивыми к изменениям и ошибкам. Таким образом, они должны использоваться по умолчанию, и только те методы, которые важны для клиентов, должны быть общедоступными.
Что ж, я могу создать объект, который имеет только частные методы и взаимодействует с другими объектами, слушая их события. Это было бы очень инкапсулировано, но совершенно не поддается проверке.
Также считается плохой практикой добавлять методы ради тестирования.
Значит ли это, что TDD расходится с инкапсуляцией? Каков соответствующий баланс? Я склонен обнародовать большинство или все мои методы сейчас ...
источник
Ответы:
Предпочитайте тестирование интерфейса, а не тестирование на реализации.
Это зависит от вашей среды разработки, см. Ниже.
Правильно, TDD фокусируется на тестировании интерфейса.
Частные методы - это детали реализации, которые могут меняться в течение любого цикла перефакторинга. Должна быть возможность перефакторинга без изменения интерфейса или поведения черного ящика . Фактически, это является частью преимущества TDD, легкость, с которой вы можете создать уверенность в том, что внутренние изменения в классе не повлияют на пользователей этого класса.
Даже если у класса нет открытых методов, его обработчики событий являются его общедоступным интерфейсом и противоречат тому общедоступному интерфейсу, который вы можете проверить.
Поскольку события являются интерфейсом, то именно эти события вам нужно будет генерировать для проверки этого объекта.
Изучите использование фиктивных объектов в качестве клея для вашей тестовой системы. Должна быть возможность создать простой фиктивный объект, который генерирует событие и улавливает результирующее изменение состояния (возможно, другим фиктивным объектом приемника).
Безусловно, вы должны быть очень осторожны в разоблачении внутреннего состояния.
Точно нет.
TDD не должен изменять реализацию ваших классов иначе как для упрощения их (применяя YAGNI с более ранней точки).
Лучшая практика с TDD идентична лучшей практике без TDD, вы просто узнаете, почему раньше, потому что вы используете интерфейс при его разработке.
Это было бы лучше, если бы ребенок вылил воду в ванну.
Вам не нужно обнародовать все методы, чтобы вы могли разрабатывать их в формате TDD. Посмотрите мои заметки ниже, чтобы увидеть, действительно ли ваши частные методы не поддаются проверке.
Более детально рассмотрим тестирование приватных методов
Если вам абсолютно необходимо выполнить модульное тестирование некоторого личного поведения класса, в зависимости от языка / среды, у вас может быть три варианта:
Очевидно, что третий вариант является безусловно лучшим.
1) Поместите тесты в класс, который вы хотите протестировать (не идеально)
Хранить тестовые случаи в том же файле класса / исходного кода, что и тестируемый производственный код, - самый простой вариант. Но без большого количества препроцессорных директив или аннотаций вы в конечном итоге получите тестовый код, который будет лишним раздувать ваш производственный код, и в зависимости от того, как вы структурировали свой код, вы можете в конечном итоге случайно раскрыть внутреннюю реализацию пользователям этого кода.
2) Выставьте приватные методы, которые вы хотите протестировать, как публичные (на самом деле не очень хорошая идея)
Как и предполагалось, это очень плохая практика, разрушает инкапсуляцию и раскрывает внутреннюю реализацию пользователям кода.
3) Используйте лучшую среду тестирования (лучший вариант, если он доступен)
В мире Eclipse, 3. может быть достигнуто с помощью фрагментов . В мире C # мы могли бы использовать частичные классы . Другие языки / среды часто имеют схожую функциональность, вам просто нужно ее найти.
Слепое предположение о том, что единственными вариантами являются 1. или 2., может привести к тому, что производственное программное обеспечение будет раздуто с помощью тестового кода или неприятных интерфейсов классов, которые стирают грязное белье в общественных местах. * 8' )
источник
Конечно, вы можете иметь частные методы, и, конечно, вы можете проверить их.
Или есть какой - то способ , чтобы получить частный метод для запуска, в этом случае вы можете проверить это таким образом, или нет нет способа , чтобы получить частные бежать, и в этом случае: почему, черт возьми , вы пытаетесь проверить это, просто удалить эту чертову вещь!
В вашем примере:
Почему это не поддается проверке? Если метод вызывается в ответ на событие, просто попросите тест передать объекту соответствующее событие.
Дело не в том, чтобы не иметь никаких закрытых методов, а в том, чтобы не нарушать инкапсуляцию. У вас могут быть частные методы, но вы должны проверить их через публичный API. Если открытый API основан на событиях, используйте события.
В более частом случае частных вспомогательных методов их можно протестировать с помощью открытых методов, которые их вызывают. В частности, поскольку вам разрешено писать код только для того, чтобы пройти неудачный тест, а ваши тесты тестируют общедоступный API, весь новый код, который вы пишете, обычно будет общедоступным. Закрытые методы появляются только в результате рефакторинга метода извлечения , когда они извлекаются из уже существующего открытого метода. Но в этом случае исходный тест, который проверяет открытый метод, по-прежнему охватывает также закрытый метод, поскольку открытый метод вызывает закрытый метод.
Таким образом, обычно закрытые методы появляются только тогда, когда они извлекаются из уже протестированных общедоступных методов и, таким образом, уже проверены.
источник
internal
методы или публичные методы вinternal
классах должны проверяться напрямую довольно часто. К счастью .net поддерживаетInternalsVisibleToAttribute
, но без него тестирование этого метода было бы PITA.Когда вы создаете новый класс в своем коде, вы делаете это, чтобы отвечать некоторым требованиям. Требования говорят, что код должен делать, а не как . Это позволяет легко понять, почему большинство тестов происходит на уровне открытых методов.
Посредством тестов мы проверяем, что код выполняет то, что от него ожидается , генерирует соответствующие исключения, когда ожидается, и т. Д. Нам не важно, как код реализован разработчиком. Хотя нас не волнует реализация, то есть то, как код делает то, что он делает, имеет смысл избегать тестирования частных методов.
Что касается тестирования классов, которые не имеют общедоступных методов и взаимодействуют с внешним миром только посредством событий, вы также можете проверить это, отправив, посредством тестов, события и прослушав ответ. Например, если класс должен сохранять файл журнала каждый раз, когда он получает событие, модульный тест отправит событие и проверит, что файл журнала записан.
И последнее, но не менее важное: в некоторых случаях вполне допустимо тестировать частные методы. Вот почему, например, в .NET вы можете тестировать не только публичные, но и частные классы, даже если решение не такое простое, как для открытых методов.
источник
Я не согласен с этим утверждением, или я бы сказал, что вы не тестируете частные методы напрямую . Открытый метод может вызывать разные частные методы. Возможно, автор хотел иметь «маленькие» методы и извлек часть кода в умно названный приватный метод.
Независимо от того, как написан открытый метод, ваш тестовый код должен охватывать все пути. Если после ваших тестов вы обнаружите, что один из операторов ветвления (if / switch) в одном частном методе никогда не охватывался вашими тестами, тогда у вас есть проблема. Либо вы пропустили случай, и реализация верна, либо реализация неправильна, и эта ветвь никогда не должна была существовать на самом деле.
Вот почему я часто использую Cobertura и NCover, чтобы убедиться, что мой тест открытых методов также охватывает частные методы. Не стесняйтесь писать хорошие ОО-объекты с закрытыми методами и не позволяйте TDD / Testing мешать вам в этом.
источник
Ваш пример все еще отлично тестируется, если вы используете Dependency Injection для предоставления экземпляров, с которыми взаимодействует ваша CUT. Затем вы можете использовать макет, сгенерировать интересующие события, а затем наблюдать, выполняет ли CUT правильные действия со своими зависимостями.
С другой стороны, если у вас есть язык с хорошей поддержкой событий, вы можете пойти немного по другому пути. Мне не нравится, когда объекты подписываются на сами события, вместо этого есть фабрика, которая создает объект, связывает события с открытыми методами объекта. Это проще для тестирования и делает его внешне видимым, для каких событий CUT должен быть проверен.
источник
Вы не должны отказываться от использования частных методов. Вполне разумно использовать их, но с точки зрения тестирования труднее непосредственно тестировать, не нарушая инкапсуляцию или добавляя специфичный для теста код в ваши классы. Хитрость заключается в том, чтобы свести к минимуму то, что, как вы знаете, заставит ваш желудок извиваться, потому что вы чувствуете, что испортили свой код.
Это то, что я имею в виду, чтобы попытаться достичь работоспособного баланса.
Думай сбоку. Держите свои классы маленькими и ваши методы меньшими, и используйте много композиции. Это звучит как большая работа, но в итоге вы получите более индивидуально тестируемые элементы, ваши тесты будут проще, у вас будет больше возможностей для использования простых макетов вместо реальных, больших и сложных объектов, надеюсь, хорошо Фактор и слабосвязанный код, и что более важно, вы дадите себе больше возможностей. Уменьшение количества вещей, как правило, экономит ваше время, потому что вы сокращаете количество вещей, которые необходимо проверять отдельно для каждого класса, и вы, как правило, уменьшаете количество спагетти кода, которое иногда может произойти, когда класс становится большим и имеет много внутренне зависимое поведение кода.
источник
Как этот объект реагирует на эти события? Предположительно, он должен вызывать методы для других объектов. Вы можете проверить это, проверив, вызваны ли эти методы. Пусть он вызывает фиктивный объект, и тогда вы легко можете утверждать, что он делает то, что вы ожидаете.
Проблема в том, что мы хотим проверить только взаимодействие объекта с другими объектами. Нам все равно, что происходит внутри объекта. Так что нет, у вас не должно быть больше публичных методов, чем раньше.
источник
Я тоже боролся с этой же проблемой. Действительно, способ обойти это так: как вы ожидаете, что остальная часть вашей программы будет взаимодействовать с этим классом? Проверьте свой класс соответственно. Это заставит вас создавать свой класс на основе того, как с ним взаимодействует остальная часть программы, и, фактически, будет стимулировать инкапсуляцию и хороший дизайн вашего класса.
источник
Вместо частного используйте модификатор по умолчанию. Затем вы можете тестировать эти методы индивидуально, а не только в сочетании с публичными методами. Это требует, чтобы ваши тесты имели ту же структуру пакета, что и ваш основной код.
источник
internal
в .net.Несколько частных методов обычно не являются проблемой. Вы просто тестируете их через публичный API, как если бы код был встроен в ваши публичные методы. Избыток частных методов может быть признаком плохой сплоченности. У вашего класса должна быть одна связная ответственность, и часто люди делают методы закрытыми, чтобы создать видимость единства, где их на самом деле не существует.
Например, у вас может быть обработчик событий, который выполняет много вызовов базы данных в ответ на эти события. Поскольку очевидно, что создание экземпляра обработчика событий для вызовов базы данных является плохой практикой, соблазн состоит в том, чтобы сделать все вызовы, связанные с базой данных, закрытыми методами, когда они действительно должны быть выделены в отдельный класс.
источник
TDD не расходится с инкапсуляцией. Возьмите самый простой пример метода или свойства геттера, в зависимости от вашего языка. Допустим, у меня есть объект Customer, и я хочу, чтобы он имел поле Id. Первый тест, который я собираюсь написать, это что-то вроде «customer_id_initializes_to_zero». Я определяю метод получения, чтобы генерировать не реализованное исключение и наблюдать за ошибкой теста. Затем, самое простое, что я могу сделать, чтобы пройти этот тест, это вернуть получатель в ноль.
Оттуда я перейду к другим тестам, предположительно, с участием идентификатора клиента, являющегося фактическим функциональным полем. В какой-то момент мне, вероятно, придется создать приватное поле, которое класс клиента использует для отслеживания того, что должно быть возвращено получателем. Как именно я отслеживаю это? Это простая поддержка int? Отслеживать ли строку и затем преобразовывать ее в int? Я отслеживаю 20 дюймов и усредняю их? Внешний мир не заботится - и ваши тесты TDD не заботятся. Это инкапсулированная деталь.
Я думаю, что это не всегда очевидно сразу при запуске TDD - вы не тестируете, что делают методы внутри себя - вы тестируете менее детальные проблемы класса. Таким образом, вы не хотите тестировать этот метод, создаете
DoSomethingToFoo()
экземпляр Bar, вызываете метод для него, добавляете два к одному из его свойств и т. Д. Вы проверяете, что после изменения состояния вашего объекта, некоторые средства доступа к состоянию изменились (или нет). Это общая схема ваших тестов: «когда я делаю X с моим тестируемым классом, я могу впоследствии наблюдать Y». Как добраться до Y, это не проблема тестов, и это то, что инкапсулировано, и именно поэтому TDD не расходится с инкапсуляцией.источник
Избегать использования? Нет .
Избегайте , начиная с ? Да.
Я заметил, что вы не спрашивали о том, нормально ли иметь абстрактные классы с TDD; если вы понимаете, как возникают абстрактные классы во время TDD, тот же принцип применим и к закрытым методам.
Вы не можете напрямую тестировать методы в абстрактных классах так же, как вы не можете напрямую тестировать частные методы, но именно поэтому вы не начинаете с абстрактных классов и частных методов; вы начинаете с конкретных классов и общедоступных API, а затем реорганизуете обычную функциональность.
источник