Чи слід уникати приватних методів, якщо виконую TDD?


99

Я тільки зараз вивчаю TDD. Наскільки я розумію, що приватні методи не піддаються застосуванню і не варто їх турбувати, оскільки загальнодоступний API надасть достатньо інформації для перевірки цілісності об'єкта.

Я деякий час розумів ООП. Я розумію, що приватні методи роблять об'єкти більш інкапсульованими, тим самим стійкішими до змін та помилок. Таким чином, вони повинні використовуватися за замовчуванням, і лише ті методи, які важливі для клієнтів, повинні бути оприлюднені.

Ну, для мене можливо зробити об’єкт, який має лише приватні методи та взаємодіє з іншими об’єктами, слухаючи їх події. Це було б дуже інкапсульовано, але абсолютно не піддавалося.

Також вважається поганою практикою додавати методи заради тестування.

Чи означає це, що TDD суперечить інкапсуляції? Який відповідний баланс? Я схильний оприлюднити більшість чи всі мої методи зараз ...


9
Погана практика та реальність в індустрії програмного забезпечення - це різні тварини. Ідеальна ситуація частіше, ніж не спотворена реальність у світі бізнесу. Робіть те, що має сенс, і дотримуйтесь цього протягом усієї програми. Я набагато скоріше маю погану практику, ніж аромат місяця, розповсюдженого в додатку.
Аарон Маківер

10
"приватні методи нестабільні"? Яка мова? У деяких мовах це незручно. В інших мовах це абсолютно просто. Крім того, ви хочете сказати, що принцип дизайну інкапсуляції завжди повинен бути реалізований за допомогою багатьох приватних методів? Це здається трохи екстремальним. Деякі мови не мають приватних методів, але все ще, здається, мають добре укладені конструкції.
С.Лотт

"Я розумію, що приватні методи роблять об'єкти більш інкапсульованими, таким чином, більш стійкими до змін та помилок. Таким чином, їх слід використовувати за замовчуванням, і лише ті методи, які мають значення для клієнтів, повинні бути оприлюднені". Мені це здається протилежним погляду на те, чого намагається досягти TDD. TDD - це методологія розробки, яка приводить вас до створення простого, працюючого та відкритого для зміни дизайну. Дивлячись "від приватного" та "лише публікувати ...", повертається повністю. Забудьте, що існує приватний метод прийняття TDD. Пізніше робіть їх за потребою; як частина рефакторингу.
herby


Тож @gnat ви вважаєте, що це слід закрити як дублікат питання, який виник із моєї відповіді на це запитання? * 8 ')
Марк Бут

Відповіді:


50

Віддайте перевагу тестуванню інтерфейсу, ніж тестуванню на реалізацію.

Наскільки я розумію, що приватні методи не піддаються конкуренції

Це залежить від вашого середовища розробки, див. Нижче.

[приватні методи] не повинні турбуватися, оскільки загальнодоступний API надасть достатньо інформації для перевірки цілісності об'єкта.

Правильно, TDD зосереджується на тестуванні інтерфейсу.

Приватні методи - це деталізація реалізації, яка може змінюватися протягом будь-якого циклу рефактора. Повинно бути можливим повторний фактор, не змінюючи інтерфейс чи поведінку чорного поля . Насправді, це є частиною переваги TDD, простота, з якою ви можете створити впевненість, що внутрішні зміни класу не вплинуть на користувачів цього класу.

Ну, для мене можливо зробити об’єкт, який має лише приватні методи та взаємодіє з іншими об’єктами, слухаючи їх події. Це було б дуже інкапсульовано, але абсолютно не піддавалося.

Навіть якщо у класу немає публічних методів, це обробники подій - це публічний інтерфейс , і це проти того публічного інтерфейсу, який ви можете протестувати.

Оскільки події є інтерфейсом, то для тестування цього об’єкта вам потрібно буде генерувати події.

Погляньте на використання макетних предметів як клею для вашої тестової системи. Повинно бути можливим створити простий макетний об’єкт, який генерує подію і підбирає результуючу зміну стану (можливо іншим об'єктом макету приймача).

Також вважається поганою практикою додавати методи заради тестування.

Абсолютно, вам слід дуже обережно викривати внутрішній стан.

Чи означає це, що TDD суперечить інкапсуляції? Який відповідний баланс?

Абсолютно ні.

TDD не повинен змінювати реалізацію ваших класів, крім того, щоб можливо спростити їх (застосувавши YAGNI з попередньої точки).

Найкраща практика з TDD є ідентичною найкращій практиці без TDD, ви просто дізнаєтеся, чому раніше, оскільки ви використовуєте інтерфейс під час його розробки.

Я схильний оприлюднити більшість чи всі мої методи зараз ...

Це було б скоріше викинути дитину разом з водою з ванни.

Вам не потрібно оприлюднювати всі методи, щоб ви могли розвиватися TDD. Перегляньте мої нотатки нижче, щоб побачити, чи справді ваші приватні методи не можна визначити.

Більш детально розглянути тестування приватних методів

Якщо ви абсолютно зобов'язані перевірити приватну поведінку класу, залежно від мови / середовища, у вас можуть бути три варіанти:

  1. Поставте тести в клас, який ви хочете пройти.
  2. Помістіть тести в інший клас / вихідний файл та відкрийте приватні методи, які ви хочете перевірити як публічні методи.
  3. Використовуйте тестове середовище, що дозволяє зберігати тестовий і виробничий код окремо, але все ж дозволяють тестувати код доступу до приватних методів виробничого коду.

Очевидно, що 3-й варіант на сьогоднішній день найкращий.

1) Поставте тести в клас, який ви хочете протестувати (не ідеально)

Зберігання тестових випадків у тому ж класі / вихідному файлі, що і тестовий код виробництва, є найпростішим варіантом. Але без великої кількості попередніх процесорних інструкцій чи приміток, ви отримаєте тестовий код, який надмірно розмиває ваш виробничий код, і залежно від того, як ви структурували свій код, ви можете випадково піддавати внутрішню реалізацію користувачам цього коду.

2) Розкрийте приватні методи, які ви хочете перевірити як публічні методи (насправді це не дуже гарна ідея)

Як було запропоновано, це дуже погана практика, руйнує інкапсуляцію та піддається внутрішній реалізації користувачам коду.

3) Використовуйте краще середовище для тестування (найкращий варіант, якщо він є)

У світі затемнення 3. можна досягти, використовуючи фрагменти . У світі C # ми можемо використовувати часткові класи . Інші мови / середовища часто мають подібний функціонал, просто потрібно його знайти.

Сліпо припустивши, що 1. або 2. є єдиними варіантами, які, ймовірно, призведуть до того, що виробниче програмне забезпечення, роздуте тестовим кодом, або неприємні інтерфейси класу, які миють брудну білизну на публіці. * 8 ')

  • Загалом - все-таки краще не перевіряти приватну реалізацію.

5
Я не впевнений, що згоден з будь-яким із запропонованих вами трьох варіантів. Моїм уподобанням було б лише перевірити публічний інтерфейс, як ви вже говорили раніше, але забезпечити, щоб цим чином застосовувалися приватні методи. Частиною перевагою цього є пошук мертвого коду, що навряд чи станеться, якщо ви змусите свій тестовий код порушити звичайне використання мови.
Маг

Ваші методи повинні робити одне, а тест не повинен розглядати реалізацію жодним чином. Приватні методи - це деталі реалізації. Якщо тестування лише публічних методів означає, що ваші тести є інтеграційними тестами, у вас є проблема з дизайном.
Маг

А як же зробити метод за замовчуванням / захищеним та створити тест у тестовому проекті з тим же пакетом?
RichardCypher

@RichardCypher Це фактично те саме, що і 2), оскільки ви змінюєте те, що у вашому методі було б ідеально для усунення дефіциту у вашому тестовому середовищі, тому, безумовно, все ще погана практика.
Марк Бут

75

Звичайно, ви можете мати приватні методи, і звичайно, ви можете їх перевірити.

Або є якийсь - то спосіб , щоб отримати приватний метод для запуску, в цьому випадку ви можете перевірити це таким чином, чи ні немає способу , щоб отримати приватні бігти, і в цьому випадку: чому, чорт візьми , ви намагаєтеся перевірити це, просто видалити прокляту річ!

У вашому прикладі:

Ну, для мене можливо зробити об’єкт, який має лише приватні методи та взаємодіє з іншими об’єктами, слухаючи їх події. Це було б дуже інкапсульовано, але абсолютно не піддавалося.

Чому це було б непереборно? Якщо метод викликається як реакція на подію, просто попросіть тест подати об'єкту відповідну подію.

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

Що стосується більш поширених випадків приватних помічників, їх можна перевірити за допомогою публічних методів, які їх викликають. Зокрема, оскільки вам дозволяється писати код лише для того, щоб пройти невдалий тест, а ваші тести тестують загальнодоступний API, весь новий код, який ви пишете, зазвичай буде відкритим. Приватні методи з'являються лише в результаті методу екстрагування методом рефакторингу , коли вони витягуються з уже існуючого публічного методу. Але в цьому випадку оригінальний тест, який тестує публічний метод, все ще охоплює приватний метод, оскільки публічний метод називає приватний метод.

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


3
Тестування за допомогою публічних методів працює добре 99% часу. Завдання полягає в 1% часу, коли ваш єдиний публічний метод має за собою кілька сотень або тисяч рядків складного коду, і всі проміжні стани є специфічними для реалізації. Після того, як вона стає достатньо складною, намагаючись вразити всі крайні випадки від публічного методу, стає в кращому випадку болісним. Крім того, тестування крайових випадків шляхом розбиття інкапсуляції та викриття більшої кількості методів як приватних, або з використанням шахрайства, щоб тести викликали приватні методи, безпосередньо призводять до крихких тестових випадків, крім того, що вони некрасиві.
Dan Neely

24
Великі, складні приватні методи - кодовий запах. Настільки складна реалізація, що її неможливо корисно розкласти на складові частини (із загальнодоступними інтерфейсами) - це проблема передбачуваності, яка розкриває потенційні проблеми дизайну та архітектури. 1% випадків, коли приватний код величезний, зазвичай виграють від переробки, щоб розкласти та викрити.
S.Lott

13
@Dan Neely подібний код є досить незаперечним незалежно - і частина написання тестових одиниць вказує на це. Усуньте стани, розділіть класи, застосуйте всі типові рефактори, а потім напишіть одиничні тести. Також із TDD як ти навіть дійшов до цього пункту? Це одна з переваг TDD, написання тестового коду стає автоматичним.
Білл К

Принаймні internalметоди або публічні методи на internalзаняттях повинні перевірятися безпосередньо досить часто. На щастя .net підтримує InternalsVisibleToAttribute, але без цього тестування цього методу було б PITA.
CodesInChaos

25

Створюючи новий код у своєму коді, ви робите це, щоб відповісти на деякі вимоги. Вимоги говорять про те, що повинен робити код, а не як . Це дозволяє зрозуміти, чому більшість тестувань відбувається на рівні публічних методів.

За допомогою тестів ми перевіряємо, що код робить те, що очікується робити , викидає відповідні винятки, коли очікується, і т. Д. Нас дійсно не цікавить, як код реалізується розробником. Хоча нас не хвилює реалізація, тобто те, як код робить те, що робить, має сенс уникати тестування приватних методів.

Що стосується тестування класів, які не мають публічних методів і взаємодіють із зовнішнім світом лише через події, ви можете також перевірити це, надіславши, тести, події та прослухавши відповідь. Наприклад, якщо клас повинен зберігати файл журналу кожного разу, коли він отримує подію, тестовий блок відправить подію і перевірить, чи записаний файл журналу.

І останнє, але не менш важливе, в деяких випадках цілком справедливо тестування приватних методів. Ось чому, наприклад, у .NET, ви можете протестувати не лише публічні, але й приватні класи, навіть якщо рішення не таке просте, як для публічних методів.


4
+1 Важливою особливістю TDD є те, що вона змушує вас перевірити, чи вимоги виконані, а не тестувати, що МЕТОДИ роблять те, що вони думають, що ви робите. Тож питання "Чи можна перевірити приватний метод" трохи суперечить духу TDD - натомість питання може бути "Чи можу я перевірити вимогу, реалізація якої включає приватні методи". І відповідь на це питання однозначно - так.
Dawood ibn Kareem

6

Наскільки я розумію, що приватні методи не піддаються конкуренції

Я не згоден з цим твердженням, або я б сказав, що ви не випробовуєте приватні методи безпосередньо . Публічний метод може викликати різні приватні методи. Можливо, автор хотів мати "маленькі" методи і витяг частину коду в хитро названий приватний метод.

Незалежно від того, як написаний публічний метод, ваш тестовий код повинен охоплювати всі шляхи. Якщо після тестів ви виявите, що один із заяв гілки (if / switch) в одному приватному методі ніколи не був охоплений у ваших тестах, у вас виникає проблема. Або ви пропустили випадок, і реалізація правильна, АБО реалізація неправильна, і ця гілка ніколи не повинна була існувати насправді.

Ось чому я багато використовую Cobertura та NCover, щоб переконатися, що мій тест на відкриті методи також охоплює приватні методи. Не соромтеся писати хороші об'єкти OO приватними методами і не дозволяйте TDD / Тестуванню перешкоджати такій справі.


5

Ваш приклад все ще ідеально перевіряється, доки ви використовуєте залежність введення, щоб надати випадки, з якими взаємодіє ваш CUT. Тоді ви можете використовувати макет, генерувати цікаві події, а потім спостерігати, чи CUT вживає правильних дій щодо своїх залежностей.

З іншого боку, якщо ви володієте мовою з хорошою підтримкою подій, ви можете піти трохи іншим шляхом. Мені не подобається, коли об’єкти підписуються на самі події, натомість є фабрика, яка створює об'єкт, що передає події загальнодоступним методам об'єкта. Це простіше перевірити, і це робить зовні видно, на які події CUT потрібно перевірити.


Це відмінна ідея ... "... фабрика, яка створює об'єкт, з'єднує події з загальнодоступними методами об'єкта. Це простіше протестувати і робить зовні видно, для яких подій потрібно перевірити CUT. "
учень

5

Вам не потрібно буде відмовлятися від використання приватних методів. Їх цілком розумно використовувати, але з точки зору тестування важче перевірити безпосередньо, не порушуючи інкапсуляцію або додавання коду, що відповідає тесту, до своїх класів. Хитрість полягає в тому, щоб звести до мінімуму речі, які ви знаєте, що зроблять ваш кишечник, тому що ви відчуваєте, що ви забруднили свій код.

Це те, що я маю на увазі, щоб спробувати досягти працездатного балансу.

  1. Мінімізуйте кількість приватних методів та властивостей, які ви використовуєте. Більшість речей, які вам потрібні для вашого класу, як правило, піддаються впливу публічно, тому подумайте, чи дійсно вам потрібно зробити цей розумний метод приватним.
  2. Зведіть до мінімуму кількість коду у своїх приватних методах - ви дійсно так чи інакше повинні робити - і непрямо випробовуйте, де можна, завдяки поведінці інших методів. Ви ніколи не очікуєте отримати 100% тестовий покрив, і, можливо, вам потрібно буде перевірити кілька значень через налагоджувач. Використання приватних методів для викидання виключень легко перевірити побічно. Приватні властивості може знадобитися перевірити вручну або іншим методом.
  3. Якщо непряма чи ручна перевірка не відповідає вам, додайте захищену подію та доступ через Інтерфейс, щоб відкрити деякі приватні речі. Це ефективно "згинає" правила інкапсуляції, але уникає необхідності фактично надсилати код, який виконує ваші тести. Недолік полягає в тому, що це може призвести до невеликого додаткового внутрішнього коду, щоб переконатися, що подія буде запущена при необхідності.
  4. Якщо ви вважаєте, що загальнодоступний метод недостатньо "захищений", перевірте, чи є способи реалізувати у ваших методах якийсь процес перевірки, щоб обмежити їх використання. Цілком ймовірно, що поки ви думаєте про це, або продумайте кращий спосіб втілити свої методи, або ви побачите інший клас, який починає формуватися.
  5. Якщо у вас є багато приватних методів, які роблять "речі" для ваших публічних методів, може бути новий клас, який очікує на вилучення. Ви можете перевірити це безпосередньо як окремий клас, але реалізувати як складений приватно в межах класу, який його використовує.

Подумайте бічно. Нехай ваші заняття будуть малі, а ваші методи меншими, і використовуйте багато композиції. Це звучить як більше роботи, але врешті-решт, у вас з’явиться більш предметів, які можна перевірити індивідуально, ваші тести будуть простішими, у вас буде більше варіантів використання простих макетів замість реальних, великих і складних об'єктів, сподіваємось, добре, розроблений і зв'язаний код, і що ще важливіше, ви дасте собі більше можливостей. Збереження малих речей, як правило, заощаджує ваш час, оскільки ви зменшуєте кількість речей, які потрібно індивідуально перевірити на кожному класі, і ви, як правило, зменшуєте спагетті з кодом, що іноді може статися, коли клас стає великим і має багато взаємозалежна поведінка коду внутрішньо.


4

Ну, для мене можливо зробити об’єкт, який має лише приватні методи та взаємодіє з іншими об’єктами, слухаючи їх події. Це було б дуже інкапсульовано, але абсолютно не піддавалося.

Як цей об’єкт реагує на ті події? Імовірно, він повинен застосовувати методи на інших об'єктах. Ви можете перевірити це, перевіривши, чи викликаються ці методи. Нехай він викликає макетний об'єкт, і тоді ви можете легко стверджувати, що він робить те, що ви очікуєте.

Проблема полягає в тому, що ми хочемо лише перевірити взаємодію об'єкта з іншими об’єктами. Нам не байдуже, що відбувається всередині об’єкта. Тож ні, у вас не повинно бути більше публічних методів, ніж раніше.


4

Я також боровся з цим самим питанням. Дійсно, спосіб її обійти: як ви очікуєте, що решта вашої програми буде взаємодіяти з цим класом? Перевірте відповідно свій клас. Це змусить вас розробити свій клас на основі того, як решта програми взаємодіє з ним і насправді сприятиме інкапсуляції та гарному дизайну вашого класу.


3

Замість приватного використання модифікатор за замовчуванням. Тоді ви можете протестувати ці методи окремо, а не просто в поєднанні з публічними методами. Це вимагає, щоб ваші тести мали ту саму структуру пакета, що і ваш основний код.


... припускаючи, що це Java.
Давуд ібн Карім

або internalв .net.
CodesInChaos

2

Кілька приватних методів зазвичай не є проблемою. Ви просто перевіряйте їх через загальнодоступний API, як ніби код вписаний у ваші загальнодоступні методи. Надлишок приватних методів може бути ознакою поганої згуртованості. Ваш клас повинен мати одну згуртовану відповідальність, і часто люди роблять методи приватними, щоб надати вигляд згуртованості там, де насправді немає.

Наприклад, у вас може бути обробник подій, який здійснює багато дзвінків до бази даних у відповідь на ці події. Оскільки, очевидно, погана практика створювати обробник подій для здійснення дзвінків до бази даних, спокуса - зробити всі виклики, пов’язані з базою даних, приватними методами, коли вони дійсно повинні бути виведені в окремий клас.


2

Чи означає це, що TDD суперечить інкапсуляції? Який відповідний баланс? Я схильний оприлюднити більшість або всі свої методи зараз.

TDD не суперечить інкапсуляції. Візьміть найпростіший приклад способу або властивості getter, залежно від мови вибору. Скажімо, у мене є об’єкт «Замовник», і я хочу, щоб він мав поле Id. Перший тест, який я збираюся написати, - це те, що говорить щось на кшталт "customer_id_initialize_to_zero". Я визначаю getter, щоб кинути не реалізований виняток і спостерігати за тим, як тест провалюється. Тоді, найпростіша річ, яку я можу зробити, щоб пройти тестовий прохід - це повернути нулю повернення геттера.

Звідти я переходжу до інших тестів, імовірно, тих, які передбачають, що ідентифікатор клієнта є фактичним, функціональним полем. У якийсь момент мені, мабуть, доведеться створити приватне поле, яке використовує клас клієнтів, щоб відслідковувати, що має повернути одержувач. Як саме я відстежую це? Це простий бек-інт? Чи слідкувати за рядком і перетворювати його в int? Чи слідкувати за 20 інтами та середньостатистично? Зовнішній світ не хвилює - і ваші тести TDD не хвилюються. Це укладена деталь.

Я думаю, що це не завжди відразу очевидно при запуску TDD - ви не тестуєте, які методи роблять внутрішньо - ви тестуєте менш детальні проблеми класу. Отже, ви не хочете тестувати, що цей метод DoSomethingToFoo()створює бар, називає метод на ньому, додає два до його властивостей і т. Д. Ви перевіряєте, що після вимкнення стану вашого об'єкта, деякий доступ до стану змінився (чи ні). Це загальна закономірність ваших тестів: "коли я роблю X до свого тестуваного класу, згодом я можу спостерігати Y". Те, як потрапити до Y, не стосується жодних тестів, і це те, що інкапсульовано, і саме тому TDD не суперечить інкапсуляції.


2

Уникати використання? Ні.
Не слід починати ? Так.

Зауважую, ви не запитували про те, чи нормально мати абстрактні класи з TDD; якщо ви розумієте, як абстрактні класи виникають під час TDD, той самий принцип застосовується і до приватних методів.

Ви не можете безпосередньо перевірити методи тестування в абстрактних класах так само, як не можете безпосередньо перевірити приватні методи, але саме тому ви не починаєте з абстрактних класів та приватних методів; ви починаєте з конкретних класів та загальнодоступних API, а потім переробляєте загальну функціональність під час руху.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.