Як уникнути необхідності тестувати приватні методи


15

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

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

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

Чи я божевільний?


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

Відповіді:


24

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

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

Особисто я віддаю перевагу методу «білого поля» до одиничних тестів. Я можу скласти тести, щоб поставити методи та класи на тестування в різні стани, які викликають цікаву поведінку в моїх державних та приватних методах, а потім стверджувати, що результати - це те, чого я очікую.

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


1
"не знущайтеся над своїми приватними методами" Так, я можу зрозуміти тестування, коли ви знущаєтесь над ними, можливо, ви перейшли через тонку тонку межу до божевільного
Ewan

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

8
@FranSevillano Якщо вам доведеться стільки заглушити чи знущатись, я би перегляну ваш загальний дизайн Щось відчувається.
Томас Оуенс

У цьому допомагає інструмент покриття коду.
Енді,

5
@FranSevillano: Існує не так багато вагомих причин для класу, який однозначно має багато залежностей. Якщо у вас є багато залежностей, напевно у вас є клас Бога.
Mooing Duck

4

Я думаю, що це дуже погана ідея.

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

Причина, через яку ви ОБРАБОТИТИ робити ці методи приватними, полягає в тому, що вони не є основними для того, що намагаються зробити ваші класи - лише помічники в тому, як ви зараз реалізуєте цю функціональність. Що стосується рефактора, ці приватні дані, ймовірно, можуть змінитись, і вони спричинить тертя, а потім рефакторинг.

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

Я пропоную вам:

  • ПЕРЕТВИТАЙТЕ, чи повинні ці методи бути приватними
  • Напишіть одноразові тести (просто щоб переконатися, що їх правильність зараз, опублікуйте їх тимчасово, щоб ви могли тестувати, а потім видалити тест)
  • Вбудовано умовне тестування у вашу реалізацію за допомогою #ifdef та тверджень (тести робляться лише у налагодженнях).

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


6
Я не погоджуюсь. IMO, це на 100% правильно для інтеграційних тестів. Але з одиничними тестами все інакше; Мета тестування блоку - визначити, де є помилка, досить вузька, щоб ви могли її швидко виправити. Я часто опиняюся в такій ситуації: у мене дуже мало публічних методів, оскільки це справді основна цінність моїх занять (як ви сказали, що слід робити). Однак я також уникаю писати 400 рядкових методів, ні державних, ні приватних. Тож мої нечисленні публічні методи можуть досягти своєї мети лише за допомогою десятків приватних методів. Це занадто багато коду, щоб "швидко виправити". Я повинен запустити налагоджувач тощо.
marstato

6
@marstato: пропозиція: спочатку почніть писати тести і передумайте про тестування одиниць: вони не знаходять помилок, але переконайтеся, що код працює так, як планував розробник.
Тімоті Тріклер

@marstato Дякую! Так. Тести завжди проходять вперше, коли ви реєструєтесь у матеріалах (або ви б цього не зареєстрували!). Вони корисні, коли ви розвиваєте код, надаючи голову, коли щось порушуєте, і якщо у вас є ДОБРІ тести регресії, вони дають вам КОМФОРТ / ВІДПОВІДЬ про те, де шукати проблеми (не в стабільних речах , а також перевірена регресія).
Льюїс Прінгл

@marstato "Мета тестування блоку - визначити, де помилка" - Саме таке непорозуміння призводить до питання ОП. Мета тестування блоку - перевірити передбачувану (і бажано задокументовану) поведінку API.
StackOverthrow

4
@marstato Назва "тест на інтеграцію" походить від тестування того, що багато компонентів працюють разом (тобто вони належним чином інтегруються). Тестування блоку - це тестування одного компонента, робить те, що він повинен робити ізольовано, що в основному означає, що його публічний API працює як документально підтверджене / необхідне. Жоден із цих термінів нічого не говорить про те, чи включаєте ви тести логіки внутрішньої реалізації як частину забезпечення інтеграції, або робота одного блоку.
Бен

3

UnitTests перевіряють поведінку громадськості, що спостерігається , а не код, де "public" означає: повернення значень та зв'язок із залежностями.

"Одиниця" - це будь-який код, який вирішує ту саму проблему (а точніше: має однакові причини зміни). Це може бути єдиний метод або купа занять.

Основна причина, чому ви не хочете тестувати, private methodsце: вони є детальними відомостями про реалізацію, і ви можете змінити їх під час рефакторингу (покращуйте свій код, застосовуючи принципи OO, не змінюючи функціональність). Це саме те, коли ви не хочете, щоб ваші одиничні тести змінювалися, щоб вони могли гарантувати, що поведінка вашого CuT не змінилася під час рефакторингу.

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

Зазвичай я відчуваю навпаки: чим менше класів (тим менше відповідальності вони несуть), тим менше залежностей у них, і тим простішими є одиничні тести як писати, так і читати.

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

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


1

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

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

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

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

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

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


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

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

1

Перш ніж відповісти на таке запитання, потрібно визначитися, чого ви насправді хочете досягти.

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

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

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

Але ви створюєте бібліотеку. Налагодити його правильну роботу може бути важко досягти. Скажімо, я хвилююсь лише того, щоб бібліотека виконувала операцію X правильно, тому у мене є тестовий блок для X. Ви, розробник, відповідальний за створення бібліотеки, реалізуєте X, комбінуючи кроки A, B і C, кожен з яких абсолютно нетривіальний. Щоб працювати у вашій бібліотеці, ви додаєте тести, щоб переконатися, що A, B і C працюють правильно. Ви хочете ці тести. Казати "у вас не повинно бути одиничних тестів для приватних методів" зовсім безглуздо. Ви хочете перевірити ці приватні методи. Можливо, хтось вам скаже, що тестування одиничних методів невірно. Але це означає лише, що ви можете їх не називати "одиничними тестами", а "приватними тестами" або так, як ви хочете їх називати.

Мова Swift вирішує проблему, яку ви не хочете виставляти A, B, C як публічні методи лише тому, що ви хочете перевірити її, надавши функціям атрибут "testable". Компілятор дозволяє викликати приватні методи перевірки з одиничних тестів, але не з нетестового коду.


0

Так, ти божевільний .... ПОДОБИЙ ФОКС!

Існує кілька способів перевірити приватні методи, деякі з яких залежать від мови.

  • рефлексія! правила робляться порушеними!
  • роблять їх захищеними, успадковують і перекривають
  • друг / InternalsVisibleTo заняття

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


Я не знаю, на моїх очах ви пояснили, що це не дуже гарна ідея, але продовжували відповідати на питання
Ліат

Я подумав, що я покрив усі основи :(
Еван,

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

0

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

Я додаю додатковий internalаксесуар (разом із позначкою InternalsVisibleToтестової збірки), чітко названий DoSomethingForTesting(parameters), щоб перевірити ці "приватні" методи.

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

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