Коли використовувати інтерфейси (тестування одиниць, IoC?)


17

Я підозрюю, що тут допустив помилку школяра, і шукаю роз'яснення. Багато класів у моєму рішенні (C #) - наважуся сказати, що більшість - я закінчив написати відповідний інтерфейс для. Наприклад, інтерфейс "ICalculator" та клас "Калькулятор", який реалізує його, хоча я ніколи не зможу замінити цей калькулятор іншою реалізацією. Крім того, більшість з цих класів проживають у тому ж проекті, що і їх залежність - вони насправді лише повинні бути internal, але в кінцевому підсумку є publicпобічним ефектом від реалізації відповідних інтерфейсів.

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

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

2) Спочатку я вважав, що для реєстрації класу в системі IoC (Castle Windsor) потрібен інтерфейс, наприклад

Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...

коли насправді я міг просто зареєструвати конкретний тип проти себе:

Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...

3) Використання інтерфейсів, наприклад параметрів конструктора для впорскування залежностей, призводить до "нещільного зв'язку".

Так я з’їхав з розуму від інтерфейсів ?! Мені відомо про сценарії, в яких ви "зазвичай" використовуєте інтерфейс, наприклад, відкриття загальнодоступного API, або такі речі, як "підключається" функціональність. У моєму рішенні є невелика кількість класів, які відповідають таким випадкам використання, але мені цікаво, чи не потрібні всі інші інтерфейси і їх слід видалити? Що стосується пункту 3) вище, чи не буду я порушувати "нещільне з'єднання", якби це зробити?

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


Я впевнений, що C # не вимагає від вас оприлюднювати клас для впровадження інтерфейсу, навіть якщо інтерфейс є загальнодоступним ...
vaughandroid

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

@Spoike, про який йдеться, це проект інтерфейсу користувача, де здавалося, що всі класи будуть внутрішніми, але вони все ще потребують тестування одиниць? Я читав деінде про те, що не потрібно тестувати внутрішні методи, але ніколи не розумів причин, чому.
Ендрю Стівенс

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

@Spoike, ви робите публіку ваших методів VM просто для того, щоб зробити їх доступними для наших тестів? Можливо, там я помиляюся, намагаючись зробити все внутрішнім. Я думаю, що мої руки можуть бути пов'язані з Moq, хоча, мабуть, потрібні заняття (а не лише методи) для публіки, щоб знущатися над ними (див. Мій коментар до відповіді Теластіна).
Ендрю Стівенс

Відповіді:


7

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

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


1
Дякую за відповідь. Як було сказано, є кілька помилкових причин, чому я робив те, що робив, хоча я знав, що більшість інтерфейсів ніколи не матимуть більше однієї реалізації. Частина проблеми полягає в тому, що я використовую Moq-фреймворк, який чудово підходить для глузування з інтерфейсами, але для знущання над класом він повинен бути загальнодоступним, мати загальнодоступний Ctrl без параметрів, а методи, для яких слід глузувати, повинні бути "public virtual").
Ендрю Стівенс

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

1
@AndrewStephens - не все на світі потребує глузування. Якщо класи достатньо міцні (або щільно з'єднані), щоб не потрібні інтерфейси, вони достатньо міцні, щоб просто використовувати як є в одиничному тестуванні. Час / складність, що їх застосовує, не вартує переваг тестування.
Теластин

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

1
@wilbert - звичайно, невеликий інтерфейс можна читати, ніж клас, але ви не вибираєте те чи інше, у вас є або клас, і клас, і інтерфейс. І ви, можливо, занадто багато читаєте мою відповідь. Я не кажу, що ніколи не створюйте інтерфейси для однієї реалізації - більшість насправді повинна, оскільки більшість взаємодій потребує такої гнучкості. Але прочитайте питання ще раз. Зробити інтерфейс для всього - це просто вантаж. IoC - це не причина. Знущання - це не причина. Вимоги є приводом для реалізації цієї гнучкості.
Теластин

4

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

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

3) Як це хибність?

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


3

Ідея IoC - зробити конкретні типи змінними. Тож навіть якщо (прямо зараз) у вас є лише один калькулятор, який реалізує ICalculator, друга реалізація може залежати лише від інтерфейсу, а не від деталей реалізації з калькулятора.

Тому перша версія вашої реєстрації на IoC-контейнер є правильною та тією, яку ви повинні використовувати в майбутньому.

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


2

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

Тестові парні здебільшого корисні для ізоляції вашого коду від зовнішнього світу (сторонні ліблі, IO диска, мережевий IO тощо) або від дійсно дорогих обчислень. Якщо ви занадто ліберальні зі своєю структурою ізоляції (вам це дійсно навіть потрібне? Ваш проект є таким складним?), Це може легко призвести до тестів, які поєднуються з реалізацією, і це робить ваш дизайн більш жорстким. Якщо ви пишете купу тестів, які підтверджують, що якийсь клас називав метод X і Y у такому порядку, а потім передав параметри a, b і c методу Z, чи дійсно ви отримуєте значення? Іноді ви отримуєте подібні тести під час тестування реєстрації або взаємодії з сторонніми libs, але це має бути винятком, а не правилом.

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

Якщо говорити конкретніше про інтерфейси, то я вважаю, що вони корисні у кількох ключових випадках:

  • Коли вам потрібно відправляти виклики методів до різних типів, наприклад, коли у вас є кілька реалізацій (це очевидно, оскільки визначення інтерфейсу більшості мов просто є сукупністю віртуальних методів)

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

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


1

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

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