The Pros & Cons of Test-Driven Development



Test-driven development (TDD) — практика, відома вже досить давно. Розробка через короткі цикли «насамперед пишемо юніт-тест, потім-код, потім проводимо рефакторинг, повторюємо» в ряді компаній прийнята в якості стандарту. Але обов'язково команда, яка досягла хорошій ступеня зрілості процесу розробки, повинна приймати TDD? Як і для більшості інших практик Extreme Programming, спори з приводу TDD досі не вщухають. Справджуються початкові витрати на навчання та впровадження TDD? Дає TDD відчутний виграш? Можна цей виграш виміряти? Чи немає випадків, коли TDD проекту шкодить? А чи є ситуації, коли без TDD вирішити завдання просто неможливо?

Про це ми поговорили з розробниками-експертами Андрієм Солнцевым asolntsev (розробник з таллінській компанії Codeborne, який практикує Extreme Programming і дотримується TDD) і Тагіром Валеевым lany (розробник у JetBrains, також розробляє опенсорсную бібліотеку StreamEx і аналізатор байткода Java HuntBugs; переконаний, що TDD — марна практика). Цікаво? Ласкаво просимо під кат!

JUG.ru Group:
– Добрий день, Андрій, добрий день, Тагір! Таллінн, Москва і Новосибірськ на зв'язку Skype. І щоб читачам стала зрозуміла ваша позиція, перше питання коротка: чи працюєте ви за TDD?

solntsevА. Солнцев:
– Так, ми працюємо за test-driven development кожен день. Тобто ми пишемо тест і код. І я вважаю, що це дуже корисна і правильна практика. В ідеалі практично всі повинні так працювати, щоб бути ефективними.

valeevТ. Валєєв:
– Я не працюю за test-driven development в тому сенсі, в якому працює Андрій. Я для нової функціональності абсолютно точно спершу пишу код, і потім тільки тести до нього. У випадку з баг-фиксами – 50/50: якщо у мене вже є готовий код і мені прийшов report, що він десь падає, я можу написати тест, що відтворює цю проблему, і потім виправити, або можу спершу виправити, а потім написати тест. Але юніт-тестів у мене код все одно виявляється покритий. Весь новий код, який я пишу, я обов'язково покриваю юніт-тестами.

JUG.ru Group:
– Test-driven development відомий дуже давно, з початку двотисячних років. Проте в масі своїй розробники не працюють за TDD, по моїм відчуттям. Чому це відбувається? Я ставив це питання Миколі Алименкову xpinjection. Він назвав дві причини. Перша – просто не вміють. Їх ніхто не вчив, як це робити правильно. І друга – помилково вважають себе крутими архітекторами: «Зараз я швиденько тут нагенерю з паттернів певну структуру, і вона відразу буде працювати». Ваша думка яка? Чому TDD не використовується масово?

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

JUG.ru Group:
– Тагір, а чому ви не працюєте за TDD?

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

Quick fix vs. Code completion
А. Солнцев:
– А чому ти думаєш, що це так? Якщо все одно ти напишеш рівно стільки ж тестів, то чому, коли їх пишеш першими, то часу йде більше?

Т. Валєєв:
– Як мінімум, у тебе не буде code completion в IDE. Якщо ти пишеш тест, до якого немає коду, то ти не можеш його писати досить ефективно.

А. Солнцев:
– Ну, це легко вирішується. Ти ж не пишеш тест від і до повністю. Ти пишеш спочатку звернення до методу, якого немає. Так, тут у тебе немає code completion, і це добре, бо тоді ти продумаєш, як метод буде називатися. Написав звернення до методу — можеш натиснути Alt+Enter, IDE сгенерируется порожній метод. Все, далі у тебе з'явиться code completion.

JUG.ru Group:
– Більш того, ти продумаєш не просто як він буде називатися, а які параметри у нього будуть і як його викликати найбільш зручним чином.

Т. Валєєв:
– Я не згоден, що для того, щоб генерувати код, використання quick-fix-ів простіше, ніж використання code completion. Але, можливо, це смаківщина.

А. Солнцев:
– Не простіше, а приблизно однаково і так, і так. Alt + Enter натиснути і в тому, і в тому випадку.

Т. Валєєв:
– Ні, я думаю, що використання quick-fix-ів складніше. Я витрачу більше часу. І натиснути треба не тільки Alt + Enter. Наприклад, я викликаю в тесті такий-то метод і, скажімо, вважаю, що з таким-то рядковим і з таким числовим параметром повинен видавати таке значення. Я написав ассерт. Методу у мене ще немає і класу теж немає. Таким чином, я повинен спершу сказати: «Створи клас, якого ще немає». Вибрати для нього пакет. Тобто я не просто натиснув Alt + Enter, я ще в діалозі заповнив пакет, область видимості класу…

А. Солнцев:
– Так всі ці речі ти все одно будеш робити так чи інакше, без різниці.

Т. Валєєв:
– Але я їх буду писати в текстовому редакторі, а не в діалозі. Добре, з класом розібралися, ось ім'я методу. В цьому випадку я натискаю Alt + Enter і отримую діалог, в якому мені пропонують заповнити назва параметрів. Так як у мене тест містить рядок і число, IDE в мене не відгадає, як у мене ці параметри повинні називатися. Тобто я повинен буду в діалозі вводити їх назви. Можливо, вона також неправильно вгадає їх типи, особливо, якщо я припускаю, що у мене там складний тип якій-небудь. Мені набагато простіше вручну набрати це в редакторі.

А. Солнцев:
– Я повторюся ще раз, це однаково по часу. Тобто ти все одно повинен будеш в кінцевому підсумку, якщо в тебе ім'я параметра складається з десяти символів, натиснути десять клавіш. У будь-якому випадку ТDD або не TDD, це буде абсолютно однаково.

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

А. Солнцев:
– Абсолютно без різниці.

Т. Валєєв:
– На мій погляд, різниця є.

Зручність використання vs. ефективність реалізації
JUG.ru Group:
– Але, колеги, все-таки за TDD говорить ще ось який факт. Якщо спочатку ти намагаєшся написати тест, тобі спочатку доведеться виконати setup. Тест – це не тільки виклик методу, це ж ще й setup. За допомогою конструктора або фабрики ти збираєшся створювати досліджуваний об'єкт? У конструктор або властивості ти будеш передавати якісь настройки? Де ти ці параметри візьмеш? І цей момент дуже цінний, тому що якщо ти відразу починаєш думати з позиції того, як це використовувати, то в тебе виходить більш красиве API.

А. Солнцев:
– Абсолютно вірно, згоден.

Т. Валєєв:
– Це дуже хороша тема. І тут якраз можна посперечатися, тому що будь API варто розглядати як з точки зору зручності використання, так і з точки зору зручності реалізації. І більш того — ефективності і взагалі можливості реалізації, тому що може виявитися, що API, зручне з точки зору використання, незручно з точки зору реалізації тому, що йому просто не вистачає даних. Я, наприклад, пишу IDE. Це те, чим я реально зараз займаюся. Мені, наприклад, потрібен новий метод, який мені знайде клас. Я хочу, щоб по імені “java.util.Collection" я знайшов клас і дізнався, які там методи. Я, звичайно, думаю: «Що мені потрібно? Мені потрібно ім'я типу String». Добре, я пишу, припустимо, метод findClass(String name), передаю йому рядок “java.util.Collection" і перевіряю, що він щось повинен знайти. Хороший, зручний тест? Куди простіше. Але коли ви почнете реалізовувати, ви зрозумієте, що “java.util.Collection" – це незрозуміло що, тому що у вас, наприклад, в різних модулях або в різних проектах може бути підключений різний JDK і це ім'я може відповідати різним класам, в яких різну кількість методів. Коли ви будете це реалізовувати, ви про це не зможете не подумати, тому що ви одразу зрозумієте, що є проблема. Потрібна прив'язка до проекту або до якого-небудь resolve scope. Відповідно, у вашому зручному способі використання просто не вистачає даних, щоб реалізувати результат. Так що написання тесту не дозволить зробити хороший API. В даному випадку ми написали тест, нам було зручно ним користуватися, але API вийшов поганий.

А. Солнцев:
– Ну і що? Не вистачає даних, так і є, але я не бачу тут ніякої проблеми. Ти починаєш писати з тіста. Написав тест так, як максимально зручно було б його використовувати. По мірі реалізації ти бачиш, що не вистачає якихось даних, ти повертаєшся на крок назад і розумієш: «Ок, так неможливо, потрібні нові дані». І ти знову доповнюєш тест і думаєш, як найбільш зручним способом передати ці дані в цей метод через конструктор, через injection service, як завгодно. Відповідно, ти доповнюєш тест, щоб передати ці дані, і починаєш далі реалізовувати. Я не бачу проблеми. Все так і є. Evolutionary design.

Т. Валєєв:
– У тому-то й справа, що ти не бачиш проблеми, а я не бачу, навіщо писати тест, щоб потім побачити, що цей тест марний, що так не запрацює? Навіщо робити ці кроки вперед, кроки назад, коли можна відразу ж написати реалізацію? І вона буде зроблена максимально зручним способом і не доведеться ні разу переписувати тест. Відразу напишеш реалізацію, потім тест. Жодного кроку назад ти не робиш. Це ефективно.

А. Солнцев:
– Ефективно для кого? Ми повертаємося до самих основ: навіщо взагалі потрібен TDD? Він дозволяє заздалегідь продумати, як найбільш зручним способом використовувати API. Ти зараз згадав у твоєму прикладі, що тобі потрібно передати, по-перше, ім'я класу, плюс потрібно якимось чином передати ще додаткові параметри, скажімо, проект, версію Java – якийсь контекст потрібно передати. І як його передати — є різні варіанти. Можна параметром методу. Можна його заинжектить в цей клас, сервіс. Можна инжектить через конструктор. Можна зробити так, щоб він просто смикав якусь статичну змінну або статичний метод звідкись. Або вантажив б їх з бази, чорт забирай. Тобто є різні варіанти. І коли ти починаєш в тесті робити setup, як Іван згадав уже, ти починаєш в цей момент продумувати: а як найбільш зручним способом передати туди всі ці речі?

Т. Валєєв:
– До речі, ти дуже цікаву річ сказав: «Статичні методи». Тобто у мене є якийсь статичний десь контекст зовні. І може виявитися, що дійсно я напишу цей свій метод findClass, у нього буде тільки один рядковий параметр, і я подумаю: «Взагалі-то в такому режимі з цим методом теж можна працювати». Тобто реалізація можлива, якщо я десь з глобального контексту зможу дістати, який у мене проект, JDK і так далі. Але з часом може виявитися, що підтримання цього глобального контексту несе більше накладних витрат. Просто в якомусь конкретному місці стає вже незрозуміло, який зараз контекст. Тут мультитредовость спливає: у мене в одному потоці – один контекст, в іншому потоці – інший контекст. Тобто почавши з міркувань зробити як зручно використовувати, якщо я буду дуже сильно сильно цьому приділяти увагу, може виявитися, що реалізація стане взагалі непідтримуваних.

А. Солнцев:
– Ти все правильно назвав. Є такі проблеми. Саме так у багатьох проектах відбувається. І я як раз хочу підкреслити, що тест дозволить виявити значно раніше, як тільки ти в своєму тесті зрозумієш, що тобі тепер перед запуском тесту потрібно якимось чином ініціалізувати глобальний контекст. Якусь статичну змінну робити. Ти відразу побачиш: «Ой, це незручно. А раптом я запущу два різних тіста і кожен повинен ініціюватися по-своєму, наприклад, паралельно?» І замість того, щоб статичну змінну ініціювати, як-то краще і зручніше передати це, припустимо, як параметр. Тест це виявить моментально. В тому-то й річ.

JUG.ru Group:
– Так, з моєї практики: якщо код стає важким для юніт-тестування, то найчастіше це відбувається саме через якихось глобальних контекстів. Тут-то і звучить сигнал, що API ми спроектували не дуже розумно, навіть не з точки зору зручності, а саме з точки зору загальної правильності. Саме так проблеми і виникають. А якщо мені треба запускати тест для різних глобальних контекстів, а мені важко їх засетапить? Якщо, чого доброго, виявиться, що юніт-тест взагалі залежить від того, яке програмне оточення встановлено на машині у розробника? Так і розкривається проблема, що ми не продумали. Що якісь речі, які ми повинні були б передавати як параметри, наприклад, вони, дійсно, залежать від глобальних контекстів.

Т. Валєєв:
– Але з останніми репліками я абсолютно не заперечую! Ви не забувайте, що я абсолютно не проти юніт-тестів. Я гаряче за юніт-тести. Тобто з останніми репліками в контексті того, що тести і код у нас вже є, і ми в якийсь момент розуміємо, що щось не так — я абсолютно згоден. Тест та код повинні бути. Але у нас розбіжності лише з приводу того, в якому порядку вони повинні з'явитися. Позиція Андрія, як я розумію, в тому, що якщо написати спершу тест, то ми трошки будемо рухатися у бік зручності використання. Моя позиція в тому, що якщо спершу не писати тест, то ми будемо зрушені в бік зручності розробки.

А. Солнцев:
– Не будемо. Не будемо зрушені. Ми не будемо ніяким чином зміщені в бік зручності розробки. Це неправда. Написання тесту раніше ніяким чином не заважає розробці, ніяким. Це — false assumption.

Т. Валєєв:
– Добре, у тебе думка така, а у мене — інше.

Дешевше, якісніше — або відразу і те, і інше?
JUG.ru Group:
– Перед нашою розмовою я думав, що він може перейти в таку фазу, коли кожен буде наполягати на своїй суб'єктивній позиції. Але чи можливий тут об'єктивний погляд, можна виміряти як-то продуктивність команди, працюючої так чи по-іншому? Я пошукав в Google і знайшов посилання на дослідження, які проводилися в Microsoft і IBM. Дві команди змушували паралельно працювати над одним і тим же проектом, одні працювали за TDD, інші працювали не за TDD. І вийшов такий висновок, що робота по TDD трохи вище, а якість за TDD відчутно вище виходить. Тобто при більш високих трудовитрати (це важливий момент, вони зазначили, що трудовитрати на розробку за TDD вище), судячи з кількості дефектів, які довелося потім виправляти, у TDD — значно вища якість. Тобто, можливо, вибір між TDD і не TDD – це звичайний вибір між ціною і якістю.

А. Солнцев:
– Я прокоментую щодо ціни та якості. Трохи більше витрат було підраховано на якомусь першому етапі, коли йде розробка. Але не забувайте, що проект на цьому не вмирає. Проект продовжує жити. Його треба підтримувати, далі якось розвивати. І проект, який з гіршою якістю, вимагає набагато більше часу і сил на підтримку, більше багів буде, складніше буде рефакторинг і так далі. І тому в кінцевому результаті через деякий кількість часу він виявиться дорожче. Тому говорити про те, що при TDD будуть вище витрати – некоректно. У довгостроковій перспективі вони менше. Це те ж саме, що прямо тут і зараз дешевше купити пальто. TDD – це дороге пальто. Прямо тут і зараз ти купиш без TDD дешеве пальто: так, зараз здається, що це дешево, але через два роки ти виявиш, що ти кожен рік повинен купувати нове пальто.

JUG.ru Group:
– Якщо тільки ти не настільки крутий, що вмієш відразу написати прекрасний код і без TDD?

Т. Валєєв:
– Я, звичайно, не пишу відразу прекрасний код. Я пишу відразу більш-менш непоганий код, потім пишу юніт-тест. І після цього я цей більш-менш непоганий код виправляю відповідно до впали юніт-тестами. І потім відправляю його також code review і доробляю після цього.

А. Солнцев:
– Але, тим не менш, виходить, що багаторазові якісь зміни все одно є так чи інакше?

Т. Валєєв:
– Багаторазові зміни в будь-якому випадку будуть. Яка різниця? Напишеш ти тест спершу, після цього ти напишеш код. Після цього ти все одно повинен запустити цю зв'язку. Ти виявиш, що тест не проходить, будеш виправляти. Те ж саме і в мене, просто я спочатку пишу код, потім тест. А потім запускаю, далі те ж саме. Думати, що ти пишеш, все одно доведеться.

Утримання задачі: «в голові», на папері або в тестах?
А. Солнцев:
– Я хотів би сказати, в чому я бачу основну користь TDD. Я бачу в цьому інструмент, який допомагає думати. Тобто в якості альтернативи TDD можна, наприклад, заздалегідь продумати в голові, що ти будеш робити. І я вважаю, що у багатьох людей саме це виходить. Вони вважають: «Навіщо мені писати тести вперед, адже я можу і в голові відразу все продумати». Може бути, Тагір саме так вважає. І, може бути, це годиться до певного моменту, поки не дуже багато змін і коли гарна свіжа голова. Дійсно, в голові можна досить детально все продумати, але починаючи з якогось моменту голова більше не справляється. Другий варіант – намалювати на папірці заздалегідь, і може бути ще варіант — з кимось обговорити. Тобто це — всі варіанти, що дозволяють заздалегідь продумати, що ти будеш писати, але, на мій погляд, з них юніт-тест найкращий, тому що він найбільш близький до коду. Все одно: якщо ти думаєш в голові і тобі здається, що все продумав ідеально, коли сідаєш і починаєш писати код, з'ясовуються нюанси, про які ти не подумав. Те ж саме на папірці: намалював все докладно, починаєш писати код, з'ясовуються нюанси, про які ти не подумав.

Т. Валєєв:
– Я приклад з findClass(...) як раз і приводив, щоб показати, що навіть якщо ти напишеш тест, все одно з'ясовуються нюанси, про які ти не подумав.

А. Солнцев:
– Ти прав: так, нюанси, звісно, в будь-якому випадку з'ясовуються, безперечно. І коли з'ясовується, що ти про то не подумав — варіант який? Повернутися назад далі в голові продумати заново? Голова просто розпухне і зламається, не зможе так багато думати. Варіант повернутися і далі перемалювати все на папірці? Це варіант, в принципі, але на мій погляд, набагато ефективніше і швидше повернутися до юніт-тесту і його дописати. Це буде простіше, ніж перемальовувати все на папірці. Простіше, швидше, ефективніше. Я хочу провести аналогію: юніт-тест, папірець і продумування в голові — це приблизно однакові інструменти.

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

А. Солнцев:
– Це теж метод. Я абсолютно з цим не сперечаюся. Так, треба розбивати. Ніякого протиріччя. Але справа в тому, що коли ти в свою голову кладеш дуже багато таких маленьких цеглинок за день, голова набагато швидше втомлюється. Питання саме в тому, де тримати цей workspace: у голові, на папірці або в тесті.

Ассерти в коді — TDD чи ні?
JUG.ru Group:
– Я сам далеко не все роблю по TDD, хоча і прагну до цього. Але в моїй практиці траплялися якісь складні завдання, які я б не «розкусив», якби відразу не вирішував їх за допомогою TDD. Є завдання, рішення яких, як я собі уявляю, виглядає як взаємодію великої кількості маленьких «сірників». Алгоритмічно або структурно складні завдання. Спочатку я повинен бути абсолютно впевнений, що кожна «шестірня» працює правильно. Потім я намагаюся запустити ці «шестерінки» в якийсь складний механізм і домагатися їх вірного взаємодії на більш високому рівні. Можливо, це питання внутрішньої переконаності, але я впевнений, що деякі завдання без TDD ніяк в мене не зважилися. Але це, можливо, в кого як. У кого-то, може бути, зважилися б. Ось ви, Тагір, як вирішуєте найбільш алгоритмічно складні завдання?

Т. Валєєв:
– Основний підхід – це, природно, розбиття завдання на більш прості. Тобто потрібно виділити які-небудь кроки. На кожному кроці у вас повинні бути інваріанти. Це деякі твердження, деякі ассерти, які на цьому етапі повинні бути вірні. Може бути, навіть простіше це запрототипировать не з допомогою юніт-тестів, а розставивши ассерти в коді, що в цьому місці у мене такий-то інваріант вірний, в цьому місці – такий-то.

А. Солнцев:
– Так це ж те ж саме. Ассерти – це те ж саме, що тести.

JUG.ru Group:
– Якщо ми продумуємо інваріанти і перетворюємо їх в ассерти в коді, то це того ж роду робота, що створення ассертов в юніт-тестах. А ви використовуєте ассерти в коді?

Т. Валєєв:
– Я їх використовую саме у випадку алгоритмічно складних завдань. І, як правило, я їх все-таки видаляю. Але це залежить від ситуації, від загальної політики проекту. Наприклад, у своїй бібліотеці StreamEx у мене були прямо зубодробильні шматки, де потрібно було распараллеливать, і щоб все це було правильно. Був мільйон приватних випадків, коли у якому порядку thread'и можуть прийти до певній точці. Я розставляв крім юніт-тестів ассерти, для тестування не тільки зовнішнього API, але і якихось внутрішніх шматків. Все це дебажил вздовж і впоперек. Коли треба було робити реліз, я ассерти прибирав. Робив я це з різних причин, в тому числі тому, що вони можуть сповільнювати, якщо вони дозволені в runtime.

JUG.ru Group:
– І ще вони можуть вносити side effects.

Т. Валєєв:
– Ну, це погані ассерти, якщо вони вносять. А ось продуктивність вони можуть просадити. Якщо людина на весь проект тримає "-ea", то навіщо йому всередині мого коду спрацювавши ассерт? Він все одно з нього користі не отримає.

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

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

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

Т. Валєєв:
– Якщо мова йде не про зовнішній API, а про внутрішню структуру алгоритму, то як вона розбита на методи – це не дуже принципово, тому що цього ніхто не бачить. Головне, щоб це було коректно і швидко. І цілком може виявитися, що всі ті місця, де ти хочеш поставити ассерти, якщо ти по цим точкам розіб'єш на методи і поставиш юніт-тести, то код буде занадто перетворений на «локшину» і його буде важче сприйняти. Я кажу про алгоритмічно складний код. Але тут, можливо, теж смаківщина. Комусь здається, що сто однорядкових методів краще, а кому-то — що десять десятистрочных теж добре.

Прописування інтерфейсів до їх реалізації — TDD чи ні?
Т. Валєєв:
– І ще я хочу додати щодо проектування API. Це, здавалося б, така штука, для якої TDD дійсно дуже корисний, тому що якщо створюється новий API, то ми відразу ж будемо думати про те, як його зручно використовувати. І з моєї практики, якщо я дійсно в голові не розумію, як зручно, так або сяк, коли у мене є варіанти, то тут мій підхід, напевно, найближче наближається до класичного TDD, але все одно я не пишу тест вперед. Я пишу виключно інтерфейси без реалізації, і під ці інтерфейси пишу тести. І цю зв'язку спершу отлаживаю. Тобто я дивлюся заздалегідь, чи буде тест зручний або незручний. Тест повинен при цьому компілюватися разом з інтерфейсом. Природно, реалізації немає. А після цього вже, коли по інтерфейсу мені здається, що зручно, я можу писати реалізацію.

А. Солнцев:
– Але це цілком допустимо. Ми теж так приблизно і працюємо.

Т. Валєєв:
– Але знову ж таки, все одно інтерфейс з'являється вперед, якщо мова йде саме про порядок написання коду.

А. Солнцев:
– Це як раз не принциповий момент. Це просто означає, що ти натиснеш Alt + Enter трохи раніше чи трохи пізніше, але, по-моєму, від цього сильно нічого не змінюється.

Т. Валєєв:
– ГАРАЗД! Виходить, принципових розбіжностей у нас немає. Тобто ми всі згодні, що потрібно тестувати і це дуже важливо, а розбіжності лише у маленьких деталях, на мій погляд, які, може бути, більш суб'єктивні.

А. Солнцев:
– Так. Може бути, так.

чи Буває так, що TDD завдає шкоди проектом?
JUG.ru Group:
– Дійсно, до деякого консенсусу ми прийшли. Головне, звичайно, що у відповідь на запитання «чи Буває на проекті шкоду від TDD? TDD – зло?», все-таки відповідь: «Ні, не зло». А зокрема, юніт-тести самі по собі — це абсолютно точно добро. І, по-моєму, всі повинні усвідомити, що немає ніякого виправдання тим, хто не використовує юніт-тести, і у кого сильно відстає реалізація від покриття юніт-тестами. Ось це — саме зло, безумовно.

А. Солнцев:
– Так!

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

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

А. Солнцев:
– Я, звісно, точно скажу, що не надмірний. З сьогоднішньої розмови я зробив висновок, що ти робиш як мінімум стільки ж рухів, а може бути більше. Тому не можу погодитися з тим, що він надмірний. А відповісти на питання: «чи Буває, коли TDD завдає шкоди?» — мені справді дуже цікаво. Я так прямо сходу не можу сказати, що у мене такі явні приклади є, але я можу собі уявити, що напевно TDD несе шкоду, коли людей примушують це робити. Наприклад, коли вони не вміють, але їм сказали: «Треба». Вони робили, робили, написали купу тестів, а в кінці взяли і все одно по-старому все зробили. Тоді дійсно вийде, що TDD – це будуть надлишкові робота.

Т. Валєєв:
– Але це ще не та річ, яку складно контролювати, якщо парне програмування не використовується. Тобто який код чоловік написав, можна контролювати на code review. А що він написав спочатку, а що потім, якщо не стояти у нього за спиною — як ти це розумієш?

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

Т. Валєєв:
– Не знаю, тут предметно сперечатися досить важко, напевно.

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

JUG.ru Group:
– Але експеримент не чистий. Ви ж вже один раз вирішували завдання в автобусі. Тепер слід було б спочатку вирішити інше завдання по TDD, а потім без TDD, щоб порівняти результати.

А. Солнцев:
– Це вірне зауваження, так. До речі, хороша ідея. Такий експеримент теж можна провести. Взагалі щодо ідеї зробити експерименти — вона чудова. Дуже хотілося б зробити, але як? Адже в одну річку не ввійти двічі? Як можна?

Т. Валєєв:
– Навіть ці результати, про які Іван згадав, що в Microsoft щось вимірювали і виявилося, що TDD дає більш якісний код: це все дуже важко об'єктивно як-то зіставити. Напевно це були різні команди, різного рівня люди і так далі. Невідомо, чи був це проект однакової складності. Дуже багато змінних.

А. Солнцев:
– Так, звичайно. Давайте подумаємо, може бути, на якийсь конференції замутим або де-небудь ще? Було б здорово. Експеримент.

JUG.ru Group:
– Експеримент в цій області – це досить складно. Я думав про те, як можна порівнювати продуктивність команд розробників. Якщо визначати продуктивність як дріб — відношення результату до роботи — то робота, як раз, можна виміряти. І навіть категоризувати: скільки я витрачав на написання юніт-тести, написання коду, рефакторинг, баг-фіксинг і так далі. А ось з чисельником все складно. До числителю ти лінійку або секундомір не приставиш. Так що питання про те, у яких команд в середньому вище продуктивність — питання складне і вирішити його, може бути, раз і назавжди не вдасться. Але взагалі ідея на конференції зробити що-небудь типу лайфкодинга, коли одна людина вирішує за TDD, а інший без TDD невелику задачу — класна. Може бути, це було б круто.

Ну що ж, дякуємо вам за розмову. Відмінна дискусія вийшла.

А. Солнцев:
– Так, дякую. Було приємно поговорити.

Т. Валєєв:
– Спасибі. Взаємно!



Якщо ви цікавитеся тестуванням, нагадуємо, що 10 грудня в готелі «Radisson Слов'янська» пройде конференція Гейзенбаг реєстрація), на якій можна буде послухати наступні доповіді.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.