Написання Тестового кодексу проти уникнення умоглядної загальності


11

Я читав деякі повідомлення в блозі сьогодні вранці, і наткнувся на це :

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

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

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

Отже, як ми можемо примирити цей компроміс? Чи варто бути умоглядно загальним для полегшення тестування / TDD? Якщо ви використовуєте тестові подвійні, чи вважаєте вони це другими реалізаціями і таким чином роблять загальність більше не спекулятивною? Чи варто розглянути більш важкий фреймворк, який дозволяє знущатися над конкретними співробітниками (наприклад, Молі проти Moq у світі C #)? Або вам слід перевірити конкретні класи та написати тести, які можна вважати "інтеграційними", до тих пір, поки ваш дизайн, природно, не вимагає поліморфізму?

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


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

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

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

@WinstonEwert Це цікава перевага динамічного набору тексту, яку я раніше не вважав тим, хто, як ви зазначаєте, зазвичай не працює на динамічно набраних мовах.
Ерік Дітріх

@CodeInChaos Я не розглядав цю різницю для цілей цього питання, хоча це розумне розрізнення. У цьому випадку ми можемо подумати про сервісні / рамкові класи лише з однією (поточною) реалізацією. Скажімо, у мене є база даних, до якої я отримую доступ з DAO. Чи не повинен я інтерфейсувати DAO, поки у мене не буде вторинна модель стійкості? (Це, мабуть, має на увазі автор публікації блогу)
Ерік Дітріх

Відповіді:


14

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


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

1
Це відповідь, яку я вважаю найбільш переконливою (і @KilianFoth додає, що чи не існує код, друга реалізація все ще існує). Я збираюся трохи відмовитися від прийняття відповіді, щоб побачити, чи хтось ще задзвенить.
Ерік Дітріх

Я також додав би, коли ти залежно від інтерфейсів тестів, загальність уже не є спекулятивною
Піт

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

3

Тестування коду взагалі непросте. Якби це було, ми б робили це все давно, і не робили такої дещо лише за останні 10-15 років. Однією з найбільших труднощів завжди було визначення того, як перевірити код, написаний згуртовано, добре обґрунтований та перевірений, не порушуючи інкапсуляцію. Директор BDD пропонує нам майже повністю зосередитись на поведінці, а в чомусь, мабуть, напрошується думати, що вам не потрібно так сильно турбуватися про внутрішні деталі, але це часто може зробити речі досить важкими для перевірки, якщо такі є багато приватних методів, які роблять "наповнені" дуже прихованим способом, оскільки це може збільшити загальну складність вашого тесту для вирішення всіх можливих результатів на більш публічному рівні.

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

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

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

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

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


1

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

З іншого боку, можуть бути причини, щоб мати SimpleInterface та єдиний ComplexThing, який реалізує його. Можуть бути фрагменти ComplexThing, які ви не хочете отримати доступними для користувача SimpleInterface. Це не завжди через перенасичений кодер OO-ish.

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


Так, я працюю мовою (мовами) з глузуючими рамками, які підтримують глузування з конкретних об'єктів. Ці інструменти вимагають різного ступеня стрибки через обручі, щоб зробити це.
Ерік Дітріх

0

Я відповім у двох частинах:

  1. Інтерфейси вам не потрібні, якщо вас цікавить лише тестування. Для цього я використовую глузуючі рамки (на Java: Mockito або easymock). Я вважаю, що розроблений вами код не повинен змінюватись для тестування. Написання тестового коду еквівалентно написанню модульного коду, тому я схильний писати модульний (тестовий) код та перевіряти лише публічні інтерфейси коду.

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


0

Я бачив багато переваг, оскільки почав програмувати більше інтерфейс, що перевищує поліморфізм.

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

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

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

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

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