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


37

Якийсь час я намагався навчитися писати одиничні тести для свого коду.

Спочатку я почав робити справжній TDD, де я б не писав жодного коду, поки спочатку не написав невдалий тест.

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

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

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

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


6
Досконалість досягається не тоді, коли більше нічого не можна додати, а коли не залишається нічого, щоб забрати. - Антуан де Сент-Екзюпері
mouviciel

12
Як можливо, що всі ваші тести неправильні? Поясніть, будь ласка, як зміна впровадження приводить до недійсності кожного тесту, який ви написали.
S.Lott

6
@ S.Lott: Тести не були помилковими, вони просто більше не були актуальними. Скажімо, ви вирішуєте частину проблеми за допомогою простих чисел, тож ви пишете клас для генерації простих чисел і пишете тести для цього класу, щоб переконатися, що він працює. Тепер ви знайдете ще одне зовсім інше рішення вашої проблеми, яке жодним чином не передбачає праймес. Цей клас і його тести тепер зайві. Такою була моя ситуація, коли 10 класів не один.
GazTheDestroyer

5
@GazTheDestroyer мені здається, що розрізнення тестового коду та функціонального коду є помилкою - все це частина одного і того ж процесу розробки. Справедливо зауважимо, що TDD має накладні витрати, які, як правило, відновлюються далі в процесі розвитку, і здається, що цей наклад у цьому випадку вам нічого не приніс. Але однаково, наскільки тести повідомили ваше розуміння недоліків архітектури? Також важливо зауважити, що вам дозволяють (з часом, заохочувати ) обробляти свої тести з часом ... хоча це, мабуть, трохи екстремально (-:
Мерф

10
Я буду семантично педантичним і згоден з @ S.Lott тут; те, що ви робили, - це не рефакторинг, якщо це призводить до викидання багатьох класів і тестів для них. Це реконструкція . Рефакторинг, особливо в сенсі TDD, означає, що тести були зеленими, ви змінили якийсь внутрішній код, перезапустили тести, і вони залишилися зеленими.
Ерік Кінг

Відповіді:


33

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

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

Інше питання - ІМХО з вашим сприйняттям. Всі ми робимо помилки, і так легко побачити в ретроспективі, що ми повинні були зробити по-іншому. Це лише те, як ми вчимося. Запишіть свої інвестиції в одиничні тести, як ціна, дізнавшись, що складання прототипів може бути важливою, і перейміть її. Просто прагніть не помилитися двічі :-)


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

3
"Я продовжував писати тести, як я це робив, оскільки саме настільки наголошує вся література TDD". Ймовірно, вам слід оновити питання з джерелом своєї ідеї, що всі тести повинні бути написані перед будь-яким кодом.
S.Lott

1
Я не маю такої ідеї, і я не впевнений, як ви це зрозуміли з коментаря.
GazTheDestroyer

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

1
@WarrenP, безумовно, є люди, які вважають, що TDD - єдиний вірний шлях (все, що можна перетворити на релігію, якщо ти постараєшся досить ;-) Я все ж вважаю за краще бути прагматичним. Для мене TDD - це один інструмент у моїй панелі інструментів, і я використовую його лише тоді, коли він допомагає, а не перешкоджає вирішенню проблем.
Péter Török

8

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


3
Мої методи взагалі не були великими, вони просто стали неактуальними з огляду на нову архітектуру, яка не мала подібності до старої архітектури. Почасти тому, що нова архітектура була набагато простішою.
GazTheDestroyer

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

1
@Kilian, повторно "обіцянка TDD полягає в тому, що вона змушує вас досягти тих же цілей швидше" - про які цілі ви звертаєтесь тут? Цілком очевидно, що написання одиничних тестів разом із самим виробничим кодом спочатку робить вас повільнішими , порівняно з просто вибиванням коду. Я б сказав, що TDD повернеться лише в довгостроковій перспективі, завдяки покращеній якості та зменшенню витрат на обслуговування.
Péter Török

@ PéterTörök - Є люди, які наполягають, що TDD ніколи не має жодних витрат, оскільки вона окупається за час, коли ви написали код. Це, звичайно, не так для мене, але, схоже, Кілліан вірить у це сам.
пн

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

6

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

Наприклад, якщо я хочу написати розв'язувач PDE (часткових диференціальних рівнянь), я напишу кілька тестів, намагаючись вирішити речі, які я можу вирішити математично. Це мої перші "одиничні" тести - читайте: функціональні тести виконуються як частина рамки xUnit. Вони не змінюватимуться залежно від того, який алгоритм я використовую для вирішення PDE. Все, що мене хвилює - це результат. Тести другого блоку будуть зосереджені на функціях, котрі використовуються для кодування алгоритму, і, таким чином, буде визначений алгоритм - скажімо Runge-Kutta. Якби я дізнався, що Рунге-Кутта не підходить, я все одно мав би тести вищого рівня (включаючи ті, які показали, що Рунге-Кутта не підходить). Таким чином, друга ітерація все-таки матиме багато тих же тестів, що і перша.

Можливо, ваша проблема стосується дизайну, а не обов'язково коду. Але без деталей сказати важко.


Це лише периферійна, але що PDE?
CVn

1
@ MichaelKjörling Я думаю, що це часткове диференціальне рівняння
foraidt

2
Чи не Брукс відкликав це твердження у своєму другому виданні?
Саймон

Як ви маєте на увазі, що ви все ще матимете тести, які показали, що Runge-Kutta не був придатним? Як виглядають ці тести? Ви маєте на увазі, що ви зберегли написаний алгоритм Runge-Kutta, перш ніж виявити, що він не підходить, і запустити тести з кінцевим кінцем з RK в суміші не вдасться?
moteutsch

5

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

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


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

1
Як вище - я вважаю, що це слід вважати як "тести та код", а не "тести на код"
Мерф

1
+1: "Не слід намагатися написати всі тести за один раз",
С.Лотт

4

Думаю, ви це сказали самі: ви не були впевнені у своєму підході до того, як почали писати всі свої тести на одиницю.

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

Іншими словами, T у TDD не повинен мати U при собі ... Він автоматизований, але є менш одиничним тестом (як у класах і методах тестування), ніж автоматизований функціональний тест: він знаходиться на тому ж рівні функціональної деталізації як архітектури, над якою ви зараз працюєте. Ви починаєте з високого рівня, маючи декілька тестів і лише функціональну велику картину, і лише врешті-решт, ви закінчуєте тисячі UT, і всі ваші класи чітко визначені в прекрасній архітектурі ...

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

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

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


3
добре сказано - це TDD, а не UTDD
Стівен А. Лоу

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

4
Як люди, які практикують TDD, правильно справляються з такими ситуаціями?
  1. враховуючи, коли прототип проти, коли кодувати
  2. усвідомивши, що тестування одиниць не є тим самим, як TDD
  3. написання тестів TDD, щоб перевірити особливість / історію, а не функціональну одиницю

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

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

Підсумовуючи: тестування блоків має фокус на впровадженні, TDD має фокус на вимогах. Вони не одне і те ж.


"TDD має фокус на вимогах" Я абсолютно не згоден з цим. Тести, які ви пишете в TDD, є одиничними тестами. Вони роблять перевіряти кожну функцію / метод. TDD робить мати акцент на покритті коду і робить турботу про тести , які виконуються швидко (і вони краще зробити, так як ви запускати тести кожні 30 секунд). Можливо, ви думали про ATDD чи BDD?
guillaume31

1
@ ian31: ідеальний приклад зв’язку UT та TDD. Потрібно не погодитись і посилатись на деякі вихідні матеріали en.wikipedia.org/wiki/Test-driven_development - мета тестів полягає у визначенні вимог до коду . BDD - це чудово. Ніколи не чув про ATDD, але на перший погляд це схоже на те, як я застосовую масштаб TDD .
Стівен А. Лоу

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

Крім того, з статті Вікіпедії ви згадали: "Передові практики тестово-керованої розробки можуть призвести до ATDD, коли критерії, визначені замовником, автоматизовані в тести прийняття, які потім керують традиційним процесом розробки тестових підрозділів (UTDD). [ ...] Завдяки ATDD команда розробників тепер має певну ціль, яку слід задовольнити, тести прийняття, завдяки чому вони постійно фокусуються на тому, що клієнт дійсно хоче від цієї історії користувача ". Що, мабуть, означає, що ATDD орієнтований насамперед на вимоги, а не на TDD (або на UTDD, як вони це заявили ).
guillaume31

@ ian31: Питання ОП про "викидання декількох сотень одиниць тестів" вказувало на плутанину масштабу. Ви можете використовувати TDD, щоб створити сарай, якщо хочете. : D
Стівен А. Лоу

3

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

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

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


2

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

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

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

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


1

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

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

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


0

Для мене одиничні тести - це також привід поставити інтерфейс під "реальне" використання (ну, такий же реальний, як одиничні тести!).

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

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

Звичайно, у вас не завжди є час на всі ці смакоти. Це саме ті випадки, коли для досягнення поставлених цілей вам знадобиться ВЕЛИКИЙ час та / або щоб тримати все під контролем.


0

Ласкаво просимо в цирк творчих розробників .


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

- Пишіть зі своїм інстинктом із речей, які ви вже знаєте , не з вашим розумом і уявою.
- І зупинись.
- Візьміть лупу та огляньте всі написані вами слова: Ви пишете "текст", оскільки "текст" знаходиться поряд із рядком, але потрібне "дієслово", "прикметник" або щось більш точне, прочитайте ще раз і відрегулюйте метод з новим значенням
. .. або, ви написали фрагмент коду, думаючи про майбутнє? видаліть його
- виправте, виконайте інші завдання (спорт, культура чи інші речі поза бізнесом), поверніться та прочитайте ще раз.
- Усі добре підходять,
- Правильно, зробіть інше завдання, поверніться і прочитайте ще раз.
- Всі добре підходять, перейдіть до TDD
- Тепер все правильно, добре
- Спробуйте орієнтир, щоб вказати на речі, які потрібно оптимізувати, зробіть це.

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

А тепер, якщо ви побачите UML, схожий на вищезазначене, ви зможете сказати
"Бос, я починаю з TDD для цього ...."
це ще одна нова річ?
"Бос, я б спробував щось, перш ніж вирішити, як я буду кодувати .." З

найкращими побажаннями від КАРІС
КАРІС

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