Основи розробки ПЗ: мета + декомпозиція = TDD
В цьому дописі я покажу як разом працюють добре сформульована мета та декомпозиція в розробці ПЗ і яким чином з’являється «розробка керована тестами» (Test Driven Development, TDD).
Як я вже писав раніше, найважливішим фактором в цілях в розробці програм, це конкретність. Щойно ми маємо конкретну мету, ми можемо застосувати простий і рекурсивний процес.
Насамперед, коли Замовник звертається з ідеєю Продукта, ми конкретизуємо її, документуючи бізнес-вимоги і контекст. Виходячи з того, що будь-який нетривіальний продукт є, врешті-решт, нетривіальним (інакше для чого тут ми, розробники?), ми далі застосовуємо декомпозицію.
Завдячуючи сорокарічній історії індустрії розробки ПЗ, нам не треба винаходити структуру декомпозиції на цьому рівні. Просто використати вже накопичений досвід. Ми деталізуємо бізнес-вимоги в вимоги до ПЗ, що складаються з
- архітектури
- типів користувачів
- моделі даних
- функцій користувача (use-cases)
- інтерфейс користувача (опис екранів та форм, якщо Продукт матиме графічний інтерфейс або буде вебовим)
- опис як все це інтегрується з іншим ПЗ і залізом (включаючи точну версію (версії) ОС, БД та веб серверів, по потребі)
- вимоги по безпеці та конфіденціальності
- вимоги до ліцензійної політики, де документується як Продукт буде розповсюджуватись Замовником і, відповідно, які сторонні компоненти сумістні по ліцензійним міркуванням
- інші вимоги, по потребу
Виходячи з того, що майже всі програмні проекти нетривіальні — тобто не вміщаються повністю в одній голові одного розробника — нам треба провести декомпозицію глибше.
Зазвичай проекти з розробки ПЗ фокусуються на функціональності. Коли вони зосереджені на даних ми звемо їх інакше — розробка БД. Коли вони про інтерфейс користувача, ми кличемо їх дизайн (інтерфейсів). Жоден з цих двох типів проектів не є темою даної статті, тому ми сфокусуємосб на функціях.
Нетривіальна (визначенні див. вище) функціональність ПЗ знову ж таки занадто складна для миттєвої реалізації і ми проводимо декомпозицію далі. Для кожної функції в переліку ми робимо детальну документацію:
- коротку назву
- мета та опис функції
- метод виклику (клік мишою, виклик REST HTTP, виклик функції в вихідному модулі, юніксовий шелл, т.п.)
- потік подій
- тести, що мають задовольняти умову «конкретності» добре сформульованої мети
- тікети, якими ми ділимо розробку функції в одиниці роботи для планування
Звичайно, ми ділимо функцію на два або більше тікета. Якщо вона складна або тести нетривіальні, ми призначаємо один тікет на кожен тест.
Тут (і тільки тут) починається власне розробка: функціональність вже добре задана, Розробник погодив з Замовником що означає корректна поведінка функції (use tests, Luke!).
Але тут є потенційна (і велика) перешкода. В будь-якому нетривіальному проекті (ми ж не займаємость тривіальними, так?) внутрішні залежності між частинами достатньо складні для того, щоб не поміщатись в голову розробника всі одночасно. Тому, в ідеалі, всі тести мають бути виконані після кожної зміни в вихідному коді.
Ручне виконання тестів швидко перетворюється на кошмар: після кожних п’яти хвилин розробки виконувати тестів на п’ять годин. Добре, що у нас є комп’ютер для таких механічних задач :)
Так, ми приходимо до необхідності автоматичних тестів. Прямо з вимоги конкретності добре сформульованої мети.
Навіть більше, ми не маємо заморочуватись з цього моменту структурою нашого продукту занадто широко. Зрозумійте мене правильно, я не кажу що не треба взагалі не думати про це. Я маю на увазі те, що ви тепер можете безпечно сфокусуватись виключно на наступному тесті, який ви реалізуєте. І навіть більше, ми можемо писати спочатку тести і потім всього навсього виконувати make test (F9 в моєму ємаксі) для того, щоб побачити що саме не так з поточною реалізацією і в якому напрямку треба рухатись.
Так, відстань між просто автоматичними тестами і розробкою, керованою тестами, дуже коротка.
Бонуси при такому підході:
- моментально відновлюється фокус розробки, вне залежності коли ви перейшли з неї до іншої задачі — 5 хвилин чи 5 років тому, чи взагалі його вперше бачите
- (бонус менеджера, як наслідок попереднього) простіше набрати персонал
- ваш Продукт тепер має скелет, що не залежить від реалізації
- (бонус розробника, як наслідок попереднього) простий рефакторинг
- миттєма поставка: вам треба лише витягти з системи контролю версій останній варіант, що проходив всі тести і поставити Замовнику
Таким чином, все вищесказане показує як два простих принципи добре сформульованої мети та декомпозиції приводять до розробки, керованої тестами.
Зверніть увагу, що це лише початок живої розробки. Викорисвоювучи розробку, керовану тестами, ви, з однаковою легкістю можете реалізувати будь-який раціональний процес розробки, від водопаду до одноденних ітерації в XP. Єдине що тепер буде неможливим — це знову падіння в хаос.
Звичайно, звички не так просто перебороти і проста вимога писати автоматичні тести часто призводить до того, що розробники пишуть тести після коду. Але це вже зовсім інша гра, в якій умови задачі підганяються під відповідь. Типу як у футболі, замість того, щоб влучити м’ячем у ворота, гравець ставить ворота на м’яч (дякую за корисну аналогію Юрію Рашковському).
Деякі посилання на нещодавні дописи по TDD і що з нею може піти не так:
- корисні критерії тестів
- якості ідеального тесту
- Багаторівневе тестування
- Як розробка, керована тестами, може піти хибним шляхом
- Python dependency injection — метод тестування
2 коментарі
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.