Як зробити тест-керовану розробку


15

Я маю лише 2+ років досвіду в розробці додатків. У ці два роки мій підхід до розвитку був такий

  1. Проаналізуйте вимоги
  2. Основний компонент / Об'єкти, Потрібні функції, Поведінка, Процес та їх обмеження
  3. Створюйте класи, відношення між ними, обмеження в поведінці та станах об'єктів
  4. Створюйте функції, обробляйте поведінкові обмеження відповідно до вимог
  5. Тест на додаток вручну
  6. Якщо зміни вимог змінюють компонент / функції, тоді тестуйте додаток вручну

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

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

  1. Я це роблю правильно? Якщо ні, що саме я повинен змінити
  2. Чи можна визначити, чи достатньо написаний тест?
  3. Чи є гарною практикою написання тесту на дуже просту функціональність, яка може бути еквівалентною 1 + 1 = 2 або це просто перегравання?
  4. Чи добре змінити функціональність і відповідно перевірити, чи змінюється вимога?

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

Ви також можете проаналізувати тестування автоматизованого модуля / інтеграції без TDD. Двоє часто плутають, але це не одне і те ж.
Андрес Ф.

Відповіді:


19

Я це роблю правильно? Якщо ні, що саме я повинен змінити

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

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

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

Відмовитися від цієї звички важко . Дійсно, справді важко. Здається, це особливо важко для досвідчених програмістів.

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

Правила такі:

  1. Напишіть рівно один новий тест, найменший тест, який, здається, вказує у напрямку рішення
  2. Бачити це не вдалося; збої компіляції зараховуються до збоїв
  3. Зробіть тест з (1) проходження, записавши найменший код реалізації у способі тестування .
  4. Рефактор для видалення дублювання та в іншому випадку, необхідного для поліпшення дизайну. Будьте чіткі щодо використання цих рухів:
    1. ви хочете новий метод - дочекайтеся часу рефакторингу, а потім… створіть нові (нетестові) методи, виконуючи один із цих способів, і нічим іншим чином:
      • бажано: виконайте метод Extract для коду реалізації, створеного відповідно до (3) для створення нового методу в тестовому класі, або
      • якщо потрібно: перемістіть код реалізації згідно (3) у існуючий метод реалізації
    2. ви хочете новий клас - дочекайтеся часу рефакторингу, а потім… створіть нетестові класи, щоб вказати призначення для методу Move і без жодної іншої причини
    3. заповнювати класи впровадження методами, виконуючи Move Method, і ніяким іншим способом

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

Коли група людей реалізує щось на кшталт гри «tic tac toe», використовуючи псевдо-TDD, вони, як правило, отримують дуже схожі конструкції, що включають якийсь Boardклас із масивом Integers × 3 × 3 . І принаймні частина програмістів насправді написала цей клас без тестів для нього, оскільки вони «знають, що їм це знадобиться» або «потребують чогось, щоб написати свої тести проти». Однак, коли ви змусите ту саму групу застосовувати TDD так, як ніби це ви бажаєте, вони часто стикаються з широким розмаїттям дуже різних конструкцій, часто не використовують нічого, навіть віддалено схожого на Board.

Чи можна визначити, чи достатньо написаний тест?

Коли вони покривають усі вимоги бізнесу. Тести - це кодування системних вимог.

Чи є гарною практикою написання тесту на дуже просту функціональність, яка може бути еквівалентною 1 + 1 = 2 або це просто перегравання?

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

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

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

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


1
Дуже чітка відповідь! З практичної точки зору, гнучкі та потужні рамки тестування дуже приємні при практиці TDD. Незважаючи на TDD, можливість автоматичного запуску тестів неоціненна для налагодження програми. Для початку роботи з TDD, неінтерактивні програми (у стилі UNIX), мабуть, найпростіші, тому що випадок використання можна перевірити, порівнявши статус виходу та вихід програми з тим, що очікується. Конкретний приклад такого підходу можна знайти в моїй бібліотеці бензину для OCaml.
Майкл Ле Барб'є Грюневальд

4
Ви говорите: "коли ти змусиш ту саму групу застосувати TDD так, як ніби це ти маєш на увазі, вони часто стикаються з широким розмаїттям самих різних конструкцій, часто не використовують нічого, навіть віддалено схожого на Раду", як ніби це добре . Мені зовсім не зрозуміло, що це добре, а може навіть погано з точки зору технічного обслуговування, оскільки це здається, що реалізація була б дуже протиінтуїтивно зрозумілою для когось нового. Чи можете ви пояснити, чому таке різноманіття впровадження є гарною справою, або, принаймні, не поганою?
Джим Клей

3
+1 Відповідь хороша тим, що вона правильно описує TDD. Однак він також показує, чому TDD - це хибна методологія: потрібна ретельна думка та чітке проектування, особливо коли стикаються з алгоритмічними проблемами. Робота TDD "в сліпому" (як це прописує TDD), роблячи вигляд, що не володіє знаннями про домен, призводить до зайвих труднощів і тупиків. Дивіться сумнозвісну розв'язку рішення судоку (коротка версія: TDD не може перемогти знання домену).
Андрес Ф.

1
@AndresF. Насправді, повідомлення блогу, з яким ви пов’язані, здається, перегукується з досвідом, який Кіт робив, коли робив TDD так, як ніби це ви маєте на увазі: коли робите "псевдо-TDD" для Tic-Tac-Toe, вони починають із створення Boardкласу з 3x3 масив ints (або щось подібне). Якщо, якщо ви змусите їх робити TDDAIYMI, вони часто в кінцевому підсумку створюють міні-DSL для захоплення знань про домен. Це, звичайно, анекдотично. Статистично та науково обгрунтоване дослідження було б добре, але як це часто буває з подібними дослідженнями, вони або занадто малі, або занадто дорогі.
Йорг W Міттаг

@ JörgWMittag Виправте мене, якщо я вас зрозумів неправильно, але ви кажете, що Рон Джеффріс робив "псевдо-TDD"? Хіба це не форма помилки "не справжнього шотландеца"? (Я погоджуюсь з вами щодо необхідності більш наукових досліджень; блог, з яким я посилався, - це лише яскравий анекдот про вражаючий збій конкретного екземпляра використання TDD. На жаль, схоже, що євангелісти TDD занадто гучні для решти з нас, щоб реально проаналізувати цю методологію та її передбачувані переваги).
Андрес Ф.

5

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

Отже, як можна ввести більше «TDD» у свій процес розробки? По-перше, я припускаю, що ваш фактичний процес розробки не завжди такий «згори вниз», як ви описали вище. Після кроку 2 ви, ймовірно, визначили деякі компоненти, незалежні від інших компонентів. Іноді ви вирішили спочатку реалізувати ці компоненти. Деталі публічного API цих компонентів, ймовірно, не відповідають лише вашим вимогам, деталі також відповідають вашим дизайнерським рішенням. Тут ви можете почати з TDD: уявіть, як ви збираєтеся використовувати компонент і як ви фактично будете використовувати API. І коли ви починаєте кодувати таке використання API у вигляді тесту, ви тільки почали з TDD.

По-друге, ви можете робити TDD навіть тоді, коли будете кодувати більше "зверху вниз", починаючи спочатку з компонентів, які залежать від інших, неіснуючих компонентів. Що вам доведеться навчитися - це спочатку «знущатися над» з цих інших залежностей. Це дозволить вам створити та протестувати компоненти високого рівня перед тим, як перейти до компонентів нижчого рівня. Дуже детальний приклад того, як робити TDD зверху вниз, можна знайти в цій публікації блогу Ральфа Вестфала .


3

Я це роблю правильно? Якщо ні, що саме я повинен змінити

У тебе все добре.

Чи можна визначити, чи достатньо написаний тест?

Так, скористайтеся інструментом покриття тесту / коду . Мартін Фаулер пропонує кілька хороших порад щодо тестового покриття.

Чи є гарною практикою написання тесту на дуже просту функціональність, яка може бути еквівалентною 1 + 1 = 2 або це просто перегравання?

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

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

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


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

3

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

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

Ще одна гарна річ - це дивитись відео в Інтернеті, де розробляється програмне забезпечення за допомогою TDD з першої лінії. Хорошим, що колись я представлявся TDD, було « Давайте граємо в TDD» від Джеймса Шора. Погляньте, це проілюструє, як працює дизайн, що виникає, які питання слід задавати собі під час написання тестів і як створюються, реконструюються та повторюються нові класи та методи.

Чи можна визначити, чи достатньо написаний тест?

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

Чи є гарною практикою написання тесту на дуже просту функціональність, яка може бути еквівалентною 1 + 1 = 2 або це просто перегравання?

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

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

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

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