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


91

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

Зокрема, у нас буде служба Web API, яка буде дуже тонкою. Основним його обов'язком буде маршал веб-запитів / відповідей та виклик базового API, що містить бізнес-логіку.

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

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

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

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


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

20
@Lee: Хто каже, що розв'язка - це завжди хороша ідея? Ви коли-небудь бачили кодову базу, в якій все передається як інтерфейс, створений з фабрики інтерфейсів, використовуючи якийсь інтерфейс конфігурації? Я маю; це було написано на Яві, і це був повний, незбагненний, баггі-хаос. Екстремальна розв'язка - це затухання коду.
Крістіан Хакл

8
Майкл Пейзерс " Ефективна робота зі спадщинним кодексом" дуже добре займається цим питанням і повинен дати вам хороше уявлення про переваги тестування навіть у новій базі коду.
l0b0

8
@ l0b0 Це дуже велика біблія для цього. Що стосується stackexchange, це не буде відповіддю на питання, але в RL я б сказав OP, щоб прочитати цю книгу (принаймні частково). О.П., отримати Ефективна робота з успадкованим кодом і читати його, принаймні , частково (або сказати своєму босові , щоб отримати його). Він стосується таких питань. Особливо, якщо ви не робили тестування і зараз ви вступаєте в нього - у вас може бути 20 років досвіду, але ви зараз будете робити речі, з якими не маєте досвіду . Про них набагато простіше читати, ніж ретельно вивчати все це шляхом проб і помилок.
Р. Шмітц

4
Дякую за рекомендацію книги Майкла Пір'я, я обов’язково підберу копію.
Лі

Відповіді:


204

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

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

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

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

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


39
Я б стверджував, що це залежить від типу змін. Існує різниця між полегшенням тестування коду та введенням гачків, що відповідають тесту, які НІКОЛИ не повинні використовуватися у виробництві. Я особисто насторожуюсь останнього, тому що Мерфі ...
Матьє М.

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

20
@Lee Безумовно, але вам потрібно врахувати, чи наявність певного типу тестів гарантує збільшення складності коду. Мій особистий досвід полягає в тому, що одиничні тести є чудовим інструментом до тих пір, коли вони не потребують кардинальних змін у дизайні, щоб пристосувати глузування. Ось де я переходжу на різні типи тестів. Писання одиничних тестів за рахунок того, щоб зробити архітектуру істотно складнішою, єдиною метою - тестування одиниць, є оглядання пупок.
Конрад Рудольф

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

29
@ChristianHackl Тести ніколи не повинні порушувати інкапсуляцію, якщо ви намагаєтеся отримати доступ до приватних, захищених або локальних змінних з одиничного тесту, ви робите це неправильно. Якщо ви тестуєте функціональність foo, ви протестуєте лише те, чи справді він працював, а не якщо локальна змінна x є квадратним коренем входу y у третій ітерації другого циклу. Якщо якась функціональність є приватною, так і нехай, ви все одно будете тестувати її транзитивно. якщо він дійсно великий і приватний? Це вада дизайну, але, мабуть, навіть це неможливо за межами C та C ++ із відокремленням заголовка.
опа

75

Це не так просто, як можна подумати. Давайте розбимо його.

  • Написання одиничних тестів, безумовно, добре.

АЛЕ!

  • Будь-яка зміна вашого коду може ввести помилку. Тож зміна коду без поважної ділової причини не є хорошою ідеєю.

  • Ваші "дуже тонкі" webapi не здаються найбільшим випадком для тестування одиниць.

  • Одночасно змінювати код і тести - це погано.

Я б запропонував такий підхід:

  1. Пишіть інтеграційні тести . Це не повинно вимагати змін коду. Це дасть вам основні тестові випадки та дозволить вам перевірити, чи будь-які подальші зміни коду не вносять помилок.

  2. Переконайтесь, що новий код перевіряється та має тести для одиниць та інтеграції.

  3. Переконайтесь, що ваш ланцюжок CI виконує тести після складання та розгортання.

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

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

EDIT : Оскільки я написав цю відповідь, ОП роз'яснила питання, щоб показати, що вони говорять про новий код, а не про модифікації існуючого коду. Я, мабуть, наївно думав, що "Чи добре тестування блоку?" аргумент був вирішений кілька років тому.

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


12
Це набагато краща відповідь, ніж прийнята. Дисбаланс у голосах викликає жахливий характер.
Конрад Рудольф

4
@Lee Тест одиниці повинен перевірити одиницю функціональності , яка може або не відповідає класу. Одиницю функціональності слід перевірити на її інтерфейсі (який може бути API в цьому випадку). Тестування може виділити дизайнерські запахи та необхідність застосовувати різні / більш рівні вирівнювання. Створіть свої системи з невеликих складових частин, їх буде простіше міркувати і перевіряти.
Уес Толеман

2
@KonradRudolph: Я думаю, пропустив момент, коли ОП додав, що це питання стосується розробки нового коду, а не зміни існуючого. Тож нічого зламати, що робить більшість цієї відповіді непридатною.
Док Браун

1
Я категорично не згоден із твердженням, що писати одиничні тести - це завжди добре. Одиничні випробування хороші лише в деяких випадках. Дурне використання одиничних тестів для тестування коду інтерфейсу (UI), вони зроблені для перевірки бізнес-логіки. Також добре писати одиничні тести для заміни відсутніх чеків компіляції (наприклад, у Javascript). Більшість кодів лише для фронтальних записів повинні писати виключно тестові, а не одиничні тести.
Султан

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

18

Проектування коду, який є властивим для перевірки, не є запахом коду; навпаки, це знак хорошого дизайну. На основі цього є кілька відомих і широко використовуваних моделей дизайну (наприклад, Model-View-Presenter), які пропонують легке (просте) тестування як велику перевагу.

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

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


Цю проблему можна вирішити за допомогою аналізу статичного коду - позначте методи (наприклад, повинні бути названі _ForTest) та перевірити базу коду на виклики з нетестового коду.
Рикінг

13

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

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

Існує відомий підхід, який намагається вирішити проблему, спочатку написавши одиничні тести - це називається Test Driven Development (TDD), і це, безумовно, може допомогти зробити код більш одиничним, перевіреним з самого початку.

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


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

2
@Lee: внесення змін в організацію завжди викликає певний опір, і їй завжди потрібен певний час, щоб адаптувати нові речі, це не нова мудрість. Це проблема людей.
Док Браун

Абсолютно. Я просто хочу, щоб нам дали більше часу!
Лі

Часто справа полягає в тому, щоб показати людям, що робити це таким чином заощадить їм час (і, сподіваємось, теж швидко). Навіщо робити щось, що вам не принесе користі?
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen: Команда може також показати ОП, що їх підхід заощадить час. Хто знає? Але мені цікаво, чи ми насправді тут не стикаємося з проблемами менш технічного характеру; ОП продовжує приходити сюди, щоб розповісти, що його команда робить неправильно (на його думку), ніби намагається знайти союзників за його справу. Насправді, було б вигідніше обговорити проект разом з командою, а не з незнайомцями на Stack Exchange.
Крістіан Хакл

11

Я приймаю питання про (необґрунтоване) твердження, яке ви робите:

щоб перевірити сервіс Web API, нам потрібно буде знущатися над цією фабрикою

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

Деякі питання, які допоможуть вам знайти альтернативний тестовий шов, який може бути більш природним:

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

Ще одне необґрунтоване твердження, яке ви висловлюєте, стосується DI:

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

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

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


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

9

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

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

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

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


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

@Lee "мінімальний код для проходження" ... ну, це може здатися трохи німим, але це буквально те, що він говорить. Наприклад, якщо у вас є тест UserCanChangeTheirPassword, то в тесті ви викликаєте функцію (ще не існує), щоб змінити пароль, і тоді ви стверджуєте, що пароль дійсно змінений. Потім ви пишете функцію, доки не зможете запустити тест, і він не кидає винятків і не має неправильного твердження. Якщо в цей момент у вас є причина додати будь-який код, то ця причина переходить до іншого тесту, наприклад UserCantChangePasswordToEmptyString.
Р. Шмітц

@Lee Зрештою, ваші тести в кінцевому підсумку стануть документацією того, що робить ваш код, за винятком документації, яка перевіряє, чи він виконаний сам, а не просто чорнилом на папері. Порівняйте також із цим питанням - метод, CalculateFactorialякий щойно повертає 120 і тест проходить. Те є мінімум. Це також очевидно не те, що було призначено, але це просто означає, що вам потрібен ще один тест, щоб висловити те, що було призначено.
Р. Шмітц

1
@Lee Невеликі кроки. Мінімальний мінімум може бути більшим, ніж ви думаєте, коли код піднімається вище тривіального. Також дизайн, який ви робите при впровадженні всієї справи відразу, може знову бути менш оптимальним, оскільки ви робите припущення щодо того, як це слід зробити, не написавши ще тестів, які це демонструють. Ще раз пам’ятайте, що спочатку код повинен вийти з ладу.
Thorbjørn Ravn Andersen

1
Також дуже важливі регресійні тести. Чи є вони для команди?
Thorbjørn Ravn Andersen

8

Якщо вам потрібно змінити код, це запах коду.

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

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

Тримайте код простий для тестування, легкий для рефактора і легкий повторне використання з самого початку, і ви не будете вбивати себе, коли вам потрібно внести зміни.

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


3
"Throwaway prototype" - кожен окремий проект коли-небудь починає життя як один із таких ... найкраще думати про речі як ніколи такого. набравши це як я .. здогадуєтесь що? ... реконструюючи прототип, що викинувся, якого не виявилося;)
Алгі Тейлор

4
Якщо ви хочете бути впевнені, що прототип, який викине, буде викинутий, напишіть його мовою прототипу, яка ніколи не буде дозволена у виробництві. Clojure і Python - хороший вибір.
Thorbjørn Ravn Andersen

2
@ ThorbjørnRavnAndersen Це змусило мене посміятися. Це означало бути копанням на цих мовах? :)
Лі

@Lee. Ні, лише приклади мов, які можуть бути неприйнятними для виробництва - як правило, тому, що ніхто в організації не може їх підтримувати, оскільки вони незнайомі з ними, і криві їх навчання круті. Якщо такі, прийнятні, виберіть інший, який не є.
Thorbjørn Ravn Andersen

4

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

Ти кажеш

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

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

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


2

У кожній інженерній дисципліні, про яку я думаю, існує лише один спосіб досягти гідного або більш високого рівня якості:

Для обліку / перевірки в проекті.

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

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


1

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

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

Тести одиниць підкреслять, де пахне деяким кодом. Я б радив прочитати « Ефективна робота Майкла С. Пір’я» зі спадковим кодексом . Незважаючи на те, що ваш проект новий, якщо він ще не має (або багато) одиничних тестів, вам, можливо, знадобляться кілька неочевидних методів, щоб ваш код міг добре перевірити.


3
Ви можете спокуситись ввести безліч непрямих шарів, щоб мати можливість протестувати, а потім ніколи не використовувати їх, як очікувалося.
Thorbjørn Ravn Andersen

1

Коротко:

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

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

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


1

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


1
"зроблено правильно" - це проблема. Мені доводиться підтримувати два проекти, де DI був зроблений неправильно (хоча і мав на меті зробити це "правильно"). Це робить код просто жахливим і набагато гіршим, ніж застарілі проекти без тестування DI та модулів. Як правильно зробити DI непросто.
січня

@Jan, що цікаво. Як вони зробили це неправильно?
Лі

1
@Lee Один проект - це сервіс, який потребує швидкого часу запуску, але на початку жахливо повільний, тому що вся ініціалізація класу робиться наперед за рамками DI (Castle Windsor в C #). Ще однією проблемою, яку я бачу в цих проектах, є змішування DI із створенням об'єктів з "новим", що йде вбік DI. Це знов ускладнює тестування і призвело до деяких неприємних умов гонки.
січня

1

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

Давайте розглянемо різницю між тестовим:

public class MyController : Controller
{
    private readonly IMyDependency _thing;

    public MyController(IMyDependency thing)
    {
        _thing = thing;
    }
}

і не перевіряється контролер:

public class MyController : Controller
{
}

У колишньому варіанті є буквально 5 додаткових рядків коду, два з яких можна автогенерувати Visual Studio. Після того, як ви налаштуєте свою структуру введення залежності, щоб замінити конкретний тип IMyDependencyпід час виконання - що для будь-якого пристойного рамки DI - це ще один єдиний рядок коду - все просто працює, за винятком того, що зараз ви можете знущатися і таким чином перевіряти контролер на вміст вашого серця .

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

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

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


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

Контролери завжди мають загальнодоступний конструктор, неявний чи ні, оскільки MVC цього вимагає. "Складне" - можливо, якщо ви не розумієте, як працюють конструктори. Інкапсуляція - так, в деяких випадках, але дискусія щодо інкапсуляції проти ДІ є постійною, дуже суб'єктивною справою, яка тут не допоможе, і, зокрема, для більшості застосувань, ІД буде служити вам краще, ніж ІМО для інкапсуляції.
Ян Кемп

Щодо громадських конструкторів: справді, це особливість використовуваних рамок. Я замислювався над більш загальним випадком звичайного класу, який не обґрунтований рамками. Чому ви вважаєте, що розгляд додаткових параметрів методу як додаткової складності дорівнює нерозумінню того, як працюють конструктори? Однак я вдячний, що ви визнаєте наявність компромісу між DI та інкапсуляцією.
Крістіан Хакл

0

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

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

Зокрема, у нас буде служба Web API, яка буде дуже тонкою. Основним його обов'язком буде маршал веб-запитів / відповідей та виклик базового API, що містить бізнес-логіку.

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

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


0

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

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

Мабуть, деякі люди мають інше уявлення про це. Я пропоную вам спробувати виправити це спочатку.

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