розробка програм. Технологія розробки програмного забезпечення через тестування Технологія TestDriven Development (tdd)
Скачать 81.5 Kb.
|
Технологія розробки програмного забезпечення через тестування 1. Технологія Test-Driven Development (TDD). Тестування ПЗ – це процес перевірки готової програми в статиці (перегляди, інспекції, налагодження вихідного коду) і в динаміці (прогін на наборі тестових даних) з метою перевірки різних шляхів виконання програми і порівняння отриманих результатів із заздалегідь заданими.Розробка через тестування (test-driven development, TDD) – техніка розробки ПЗ, яка ґрунтується на повторенні дуже коротких циклів розробки: спочатку пишеться тест, який здійснюватиме бажані зміни в коді програми, потім пишеться код, який буде проводити даний тест і під кінець здійснюється рефакторинг нового коду до відповідних стандартів. Кент Беквважається винахідником цієї техніки, затвердженої в 2003 році, він стверджує, що розробка через тестування заохочує простий дизайн і вселяє впевненість у розробника ПЗ. Тест – це процедура, яка дозволяє або підтвердити, або спростувати працездатність коду. Коли програміст перевіряє працездатність розробленого ним коду, він виконує тестування вручну. У даному контексті тест складається з двох етапів: стимулювання коду і перевірки результатів його роботи. Автоматичний тест виконується інакше: замість програміста стимулюванням коду і перевіркою результатів займається комп'ютер, який відображує на екрані результат виконання тесту: код працездатний або код непрацездатний. Методика розробки через тестування дозволяє отримати відповіді на питання про організацію автоматичних тестів і вироблення певних навиків тестування.Як вже було сказано, розробка в стилі TDD складається з коротких циклів (тривалістю від 2 хвилин, залежно від досвідченості і стилю роботи програміста). Кожен цикл складається з наступних кроків: 1. Із репозиторія витягається програмна система, що знаходиться в погодженому стані, коли увесь набір модульних тестів виконується успішно. 2. Додається новий тест. Він може полягати в перевірці, чи реалізує система деяку нову поведінку або чи містить деяку помилку, про яку нещодавно стало відомо. 3. Успішно виконується увесь набір тестів, окрім нового теста, який не виконується. Цей крок потрібний для перевірки самого теста – чи включений він в загальну систему тестування і чи правильно відбиває нову вимогу до системи, якій вона ще не задовольняє. 4. Програма змінюється з тим, щоб якнайскоріше виконувалися усі тести. Треба додати найпростіше рішення, що задовольняє новому тесту, і одночасно з цим не зіпсувати існуючі тести. Велика частина небажаних побічних і віддалених ефектів від змін, що вносяться в програму, відстежується саме на цьому етапі, за допомогою досить повного набору тестів. 5. Увесь набір тестів виконується успішно. 6. Тепер, коли потрібна в цьому циклі функціональність досягнута найпростішим способом, програма перебудовується (за допом. Рефакторингу) для поліпшення структури і усунення надмірного, дубльованого коду. 7. Увесь набір тестів виконується успішно. 8. Комплект змін, зроблених в цьому циклі в тестах і програмі заноситься в репозиторій (операція commit), після чого програма знову знаходиться в погодженому стані і містить чітко відчутне поліпшення в порівнянні з попереднім станом. 2. Цикл розробки ПЗ через тестування. Тепер розглянемо цикл розробки ПЗ через тестування у вигляді блок-схеми, та більш детально опишемо кожен крок циклу:Вимоги. Розробка через тестування вимагає від розробника створення автоматизованих модульних тестів, що визначають вимоги до коду безпосередньо перед написанням самого коду. Тест містить перевірки умов, які можуть або виконуватися, або ні. Коли вони виконуються, говорять, що тест пройдений. Проходження тесту підтверджує поведінку, передбачувану програмістом. Розробники часто користуються бібліотеками для тестування (testing frameworks) для створення і автоматизації запуску наборів тестів. На практиці модульні тести покривають критичні і нетривіальні ділянки коду. Це може бути код, який схильний до частих змін, код, від роботи якого залежить працездатність великої кількості іншого коду, або код з великою кількістю залежностей. Середовище розробки повинне швидко реагувати на невеликі модифікації коду. Архітектура програми повинна базуватися на використанні безлічі сильно пов'язаних компонентів, які слабо зчеплені один з одним, завдяки чому тестування коду спрощується. TDD не лише припускає перевірку коректності, але і впливає на дизайн програми. Спираючись на тести, розробники можуть швидше представити, яка функціональність потрібна користувачеві. Таким чином, деталі інтерфейсу з'являються задовго до остаточної реалізації рішення. Зрозуміло, до тестів застосовуються ті ж вимоги стандартів кодування, що і до основного коду. Додавання тесту При розробці через тестування, додавання кожної нової функціональності (feature) в програму, розпочинається з написання тесту. Неминуче цей тест не проходитиме, оскільки відповідний код ще не написаний. (Якщо ж написаний тест пройшов, це означає, що або запропонована "нова" функціональність вже існує, або тест має недоліки) Щоб написати тест, розробник повинен чітко розуміти вимоги, що пред'являються до нової програми. Для цього розглядаються можливі сценарії використання і користувацькі історії. Нові вимоги можуть також спричинити зміну існуючих тестів. Це відрізняє розробку через тестування від техніки, коли тести пишуться після того, як код вже написаний: вона примушує розробника сфокусуватися на вимогах до написання коду – тонка, але важлива відмінність.Запуск усіх тестів: переконатися, що нові тести не проходять На цьому етапі перевіряють, що тільки що написані тести не проходять. Цей етап так само перевіряє самі тести: написаний тест може проходити завжди і відповідно бути даремним. Нові тести повинні не проходити із з'ясовних причин. Це збільшить упевненість (хоча не гарантуватиме повністю), що тест дійсно тестує те, для чого він був розроблений. Написати код На цьому етапі пишеться новий код так, що тест проходитиме. Цей код не обов'язково має бути ідеальний. Допустимо, щоб він проходив тест не дуже «елегантним» способом. Це прийнятно, оскільки подальші етапи поліпшать і «відполірують» його. Важливо писати код, призначений саме для проходження тесту. Не слід додавати зайвої і, відповідно, не тестованої функціональності. Запуск усіх тестів: переконатися, що усі тести проходять Якщо усі тести проходять, програміст може бути упевнений, що код задовольняє усім тестованим вимогам. Після цього можна приступити до завершального етапу циклу. Рефакторинг Коли досягнута необхідна функціональність, на цьому етапі код може бути почищений. Рефакторинг – процес зміни внутрішньої структури програми, що не зачіпає її зовнішньої поведінки і має на меті полегшити розуміння її роботи, усунути дублювання коду, полегшити внесення змін в найближчому майбутньому. Повторити цикл Описаний цикл повторюється, реалізовуючи все нову і нову функціональність. Кроки слід робити невеликими, від 1 до 10 змін між запусками тестів. Якщо новий код не задовольняє новим тестам або старі тести перестають проходити, програміст повинен повернутися до попереднього кроку. При використанні сторонніх бібліотек не слід робити надто великі зміни, які буквально тестують саму сторонню бібліотеку, а не код, який її використовує, хіба, що ви впевненні, що бібліотека не містить помилок. Стиль розробки Розробка через тестування тісно пов'язана з такими принципами як "роби простіше"(keep it simple, KIS) і "вам це не знадобиться"(you ain't gonna need it, YAGNI). Дизайн може бути чистіший і ясніший, при написанні лише того коду, який потрібний для проходження тесту.Кент Бек також пропонує принцип "підроби, поки не зробиш"(fake it till you make it). Тести повинні писатися для тестованої функціональності. Це допомагає переконатися, що додаток придатний для тестування, оскільки розробникові доведеться із самого початку обдумати те, як додаток тестуватиметься. Це також сприяє тому, що тестами буде покрита уся функціональність. Коли функціональність пишеться до тестів, розробники і організації схильні переходити до реалізації наступної функціональності, не протестувавши існуючу. Ідея перевіряти, що знову написаний тест не проходить, допомагає переконатися, що тест реально щось перевіряє. Тільки після цієї перевірки слід приступати до реалізації нової функціональності. Цей прийом, відомий як "червоно-зелений рефакторинг". Під червоним тут розуміють тести, що не пройшли, а під зеленим, навпаки, ті що пройшли. Відпрацьовані практики розробки через тестування привели до створення техніки "розробка через приймальне тестування" (Acceptance Test-driven development, ATDD), в якому критерії описані замовником автоматизуються в приймальні тести, використовувані потім в звичайному процесі розробки через модульне тестування (unit test-driven development, UTDD). Цей процес дозволяє гарантувати, що додаток задовольняє сформульованим вимогам. При розробці через приймальне тестування, команда розробників сконцентрована на чіткому завданні: задовольнити приймальні тести, які відбивають відповідні вимоги користувача. Приймальні (функціональні) тести (англ. customer tests, acceptance tests) – тести, перевіряючі функціональність додатка на відповідність вимогам замовника. Приймальні тести проходять на стороні замовника. Це допомагає йому бути упевненим в тому, що він отримає усю необхідну функціональність. 3. Переваги та недоліки TTD. Переваги: Дослідження 2005 року показало, що використання розробки через тестування припускає написання більшої кількості тестів, у свою чергу, програмісти, що пишуть більше тестів, схильні бути продуктивнішими. Гіпотези, які зв'язують якість коду з TDD були непереконливі. Програмісти, використовуючі TDD на нових проектах, відмічають, що вони рідше відчувають необхідність використати компілятор. Якщо деякі з тестів несподівано перестають проходити, відкат до останньої версії коду, яка проходить усі тести, може бути продуктивнішим, ніж компіляція.Розробка через тестування пропонує більше, ніж просто перевірка коректності, вона також впливає на дизайн програми. Спочатку сфокусувавшись на тестах, простіше представити, яка функціональність потрібна користувачеві. Таким чином, розробник продумує деталі інтерфейсу до реалізації. Тести примушують робити свій код більше пристосованим для тестування. Наприклад, відмовлятися від глобальних змінних, одинаків (singletons), робити класи менш пов'язаними і легкими для використання. Сильно пов'язаний код або код, який вимагає складної ініціалізації, буде значно важче протестувати. Модульне тестування сприяє формуванню чітких і невеликих інтерфейсів. Кожен клас виконуватиме певну роль, як правило невелику. Як наслідок, залежності між класами знижуватимуться, а зачеплення підвищуватися. Контрактне програмування (Design by Contract) доповнює тестування, формуючи необхідні вимоги через твердження (assertions). Попри те, що при розробці через тестування вимагається написати більшу кількість коду, загальний час, витрачений на розробку, зазвичай виявляється меншим. Тести захищають від помилок. Тому час, що витрачається на компіляцію, знижується багаторазово. Велика кількість тестів допомагає зменшити кількість помилок в коді. Усунення дефектів на більш ранньому етапі розробки, перешкоджає появі хронічних і дорогих помилок, що призводять до тривалої і стомливої компіляції надалі. Тести дозволяють здійснювати рефакторинг коду без ризику його зламати. При внесенні змін до добре протестованого коду, ризик появи нових помилок значно нижчий. Якщо нова функціональність призводить до помилок, тести, якщо вони звичайно є, відразу ж це покажуть. При роботі з кодом, на який немає тестів, помилку можна виявити через значний час, коли з кодом працювати буде набагато складніше. Добре протестований код легко переносить рефакторинг. Упевненість в тому, що зміни не зламають існуючу функціональність, надає упевненість розробникам і збільшує ефективність їх роботи. Якщо існуючий код добре покритий тестами, розробники почуватимуть себе набагато вільніше при внесенні архітектурних рішень, які покликані поліпшити дизайн коду. Розробка через тестування сприяє більш модульному, гнучкому і розширюваному коду. Це пов'язано з тим, що при цій методології розробникові необхідно думати про програму, як про безліч невеликих модулів, які написані і протестовані незалежно і лише потім сполучені разом. Це призводить до менших, більше спеціалізованих класів, зменшення зв'язаності і чистіших інтерфейсів. Використання mock-об'єктів також вносить вклад в модуляризацию коду, оскільки вимагає наявності простого механізму для перемикання між mock - і звичайними класами. Оскільки пишеться лише той код, що потрібний для проходження тесту, автоматизовані тести покривають усі шляхи виконання. Наприклад, перед додаванням нового умовного оператора, розробник повинен написати тест, що мотивує додавання цього умовного оператора. В результаті, отриані тести, які були розроблені методикою TDD, досить повні: вони виявляють будь-які неумисні зміни поведінки коду. Тести можуть використовуватися як документація. Хороший код розповість про те, як він працює, краще за будь-яку документацію. Документація і коментарі в коді можуть застарівати. Це може збивати з пантелику розробників, що вивчають код. А оскільки документація, на відміну від тестів, не може сказати, що вона застаріла, такі ситуації, коли документація не відповідає дійсності – не рідкість. Слабкі місця Найпершим недоліком цього підходу є те, що до нього складно звикнути ментально. Безперечно існують завдання, які неможливо (принаймні, на даний момент) вирішити тільки за допомогою тестів. Зокрема, TDD не дозволяє механічно продемонструвати адекватність розробленого коду в області безпеки даних і взаємодії між процесами. Безумовно, безпека грунтована на коді, в якому не повинно бути дефектів, проте вона грунтована також на участі людини в процедурах захисту даних. Тонкі проблеми, що виникають в області взаємодії між процесами, неможливо з упевненістю відтворити, просто запустивши деякий код. Розробку через тестування складно застосовувати в тих випадках, коли для тестування потрібне проходження функціональних тестів. Прикладами може бути: розробка інтерфейсів користувача, програм, працюючих з базами даних, а також приклад того, що залежить від специфічної конфігурації мережі. Розробка через тестування не припускає великого об'єму роботи по тестуванню такого роду речей. Вона зосереджується на тестуванні окремо взятих модулів, використовуючи mock-об'єкти, щоб представити зовнішній світ. Вимагається більше часу на розробку і підтримку, а підтримка з боку керівництва дуже важлива. Якщо в організації немає упевненості в тому, що розробка через тестування поліпшить якість продукту, то час, який втрачений на написання тестів, може розглядатися як витрачений даремно. Модульні тести, що створюються при розробці через тестування, зазвичай пишуться тими ж, хто пише тестований код. Якщо розробник неправильно представить вимоги до програми, і тест, і тестований модуль міститимуть помилки. Велика кількість використовуваних тестів може давати неправдиве відчуття надійності, що призводить до меншої кількості дій з контролю якості. Рівень покриття тестами коду, що отримується в результаті розробки через тестування, не може бути легко отриманий потім. Початкові тести стають усе більш цінними з часом. Якщо погана архітектура, поганий дизайн або погана стратегія тестування призводять до великої кількості непройдених тестів, важливо їх всіх виправити в індивідуальному порядку. Просте видалення, відключення або поспішна зміна тестів може привести до невиявлених пропусків в покритті тестами коду. Рефакторинг призводить до переписування тестів. 4. Базові інструменти тестувальника. 1. Персональний комп`ютер, терміни або робоча станція. Ніколи не зневажайте допомогою комп'ютера – звертайтеся до нього щоразу, коли в цьому виникає потреба. Значно підвищує продуктивність одночасна робота із двома комп'ютерами, на одному з яких запущена тестуюча програма, а інший служить для реєстрації проблем і коректування плана тестування. 2. Добрий текстовий процесор. Вам необхідна програма, що дозволяє вводити й редагувати плани тестування, звіти, записи й листи. 3. Процесор планів. Ця програма спеціально призначена для складання й реорганізації ієрархічних структур, і вона набагато краще справляється із цією роботою, чим звичайний текстовий процесор. З її допомогою зручно складати плани тестування, списки функцій, докладні звіти про стан і списки задач. Вибираючи такий продукт, насамперед зверніть увагу на функції групування, сортування й реорганізації інформації. 4. Електронна таблиця. Ця програма незамінна для роботи з тестовими таблицями. 5. Утиліти порівняння файлів. Подібних програм досить багато: вони порівнюють пару файлів, повідомляють, чи знайдені відмінності, і якщо так, показують їхній список. Кращі з них генерують перелік змін, які необхідно внести в один з файлів, щоб одержати інший. Найпростіші утиліти порівняння файлів нерідко поставляються в комплекті операційних систем. Утиліти можуть призначатися для різних цілей: порівняння двійкових файлів (об'єктного коду, графіки, стислих даних і т.п.) і порівняння тексту. Від їхнього призначення залежить те, у якому виді будуть представлені результати порівняння. 6. Переглядувачі файлів. Такі програми дозволяють переглядати дані, що зберігаються у файлах найрізноманітніших форматів. 7. Конвертери файлів. Ці утиліти перетворюють файли даних (текстові, графічні й т.п.) з одного формату в іншій. Наприклад, текст може бути перетворено з формату одного текстового процесора у формат іншого. 8. Утиліти для створення копій екрана. Ці утиліти дозволяють зберегти вміст екрана або поточного вікна у файлі на диску. Можливо, вам знадобиться навіть кілька таких програм, оскільки деякі з них несумісні з окремими типами програмного забезпечення. Копії екрана корисні, як для аналізу помилок і пошуку їх джерела, так і для того, щоб показати помилку програмістові. Нерідко простіше надати йому копію екрана, чим описувати його вміст на словах. 9. Утиліти для пошуку тексту. За допомогою такої утиліти можна знайти в об'єктному коді програми текстові рядки. Найпростіші з них просто прочісують програмний файл і записують в окремому текстовому файлі всі рядки ASCII, що містяться в ньому. У результаті виходить список усього вбудованого в програму тексту й повідомлень про помилки. Можливо, програміст або керівник проекту буде переконувати вас, що не варто витрачати часу на подібну роботу, оскільки весь текст зберігається у файлах ресурсів. Однак це твердження не заважає перевірити, оскільки програміст може все-таки вставити в програму парочку коротких повідомлень, особливо про критичні помилки. 10. Обладнання відеозапису (VCR). Увесь вивід на екран можна записати на відеоплівку. Для цього потрібний звичайний відеомагнітофон і спеціальна відеоплата з відповідним виходом (майте на увазі, що плати NTSC не зберігають на стрічці весь екран VGA.) Для тестування нестійких програм із важкозловимими помилками записуючі обладнання просто незамінні. Вони неймовірно полегшують пошук і відтворення помилок, а якщо помилку неможливо відтворити, відеозапис підтверджує, що вона все-таки відбулася, і зберігає точну хронологію подій. Звичайно цього досить, щоб програміст міг проаналізувати ситуацію й виявити причину збою. Однак, незважаючи на всю корисність цього інструмента, його застосування може стати для тестувальника великою проблемою. У захваті від таких можливостей, керівник проекту може вимагати, щоб відеозапис додавався до кожної виявленої помилки, а це вже серйозно загальмує вашу роботу. Тому дотримуйтеся золотої середини, застосовуючи цей засіб тільки там, де воно дійсно необхідно. 11. Діагностичні програми. Існує ряд утиліт, що аналізують апаратне й програмне забезпечення системи. Вони допоможуть вам з'ясувати, яке у вашім розпорядженні є обладнання, все чи з ними в порядку і як вони працюють. Зокрема, з їхньою допомогою можна знайти й виключити з використання ушкоджені ділянки жорсткого диска або визначити, чи працює відеоплата в режимі, що дозволяє здійснювати відеозапис роботи. Можна також зберігати інформацію про стан оперативної пам'яті в момент збою – у певних випадках ця інформація може бути дуже корисна програмістові. Він скаже вам, якою утилітою найкраще для цього скористатися, і надасть її якщо буде потреба. 12. Таймер. Іноді необхідно визначити тривалість якої-небудь операції, можливо в десятих або сотих секунди. Для цього прекрасно підійде програмний таймер. Окремі обладнання у вигляді ручного годиника менш зручніші, і з їхньою допомогою не можна працювати з дуже маленькими часовими інтервалами. Найчастіше зміни часу необхідні при тестуванні тайм-аутів, умов гонок, а також затримок реакції системи на дії користувача та виконання довготривалої обробки даних. 13. Система відстежування проблем. Служить для виправлення максимально-можливого числа помилок за рахунок того, що система представляє собою середовище відстежування виконаної роботи, середовище організації взаємодії між співробітниками та відображає продуктивність роботи кожного співробітника. 14. Програміст. Якщо вам не вдається відтворити помилку, ви не знаєте, які граничні умови процесу або не розумієте, як тестувати будь-яку функцію програми, звертайтеся до програміста. Втім, це крайній випадок. В основному ж програміст може заощадити вам годинни і дні роботи, або відразу визначивши джерело проблеми, або написавши спеціальний діагностичний код, або просто роз'яснивши деякі особливості програми й порадивши, як або якими засобами її можна ефективніше протестувати. 5. Fake -, mock- об'єкти і інтеграційні тести. Модульні тести називаються модульними, тому що вони тестують кожен модуль окремо. Не важливо, містить модуль сотні тестів або декілька. Тести, використовувані при розробці через тестування, не повинні перетинати межі процесу, і використовувати мережеві з'єднання. Інакше проходження тестів займатиме великий час, і розробники рідше запускатимуть набір тестів цілком. Введення залежності від зовнішніх модулів або даних також перетворює модульні тести на інтеграційні. При цьому якщо один модуль в ланцюжку поводиться неправильно, може бути не відразу зрозуміло який саме. Коли код, що розробляється, використовує бази даних, веб-сервіси або інші зовнішні процеси, має сенс виділити ту частину, яка покривається модульними тестами окремо. Це робиться в два кроки: Скрізь, де потрібно доступ до зовнішніх ресурсів, має бути оголошений інтерфейс, через який цей доступ здійснюватиметься. Інтерфейс повинен мати дві реалізації. Перша надає доступ до ресурсу, друга являється або fake- об`єктом, або mock-об'єктом. Все, що роблять fake-об'єкти, це добавляють повідомлення виду "Об'єкт person збережений" у лог, щоб потім перевірити правильність поведінки. Mock-об'єкти відрізняються від fake -, тим, що самі містять твердження (assertion), перевіряючі поведінку коду, який тестується. Методи fake - і mock- об'єктів, які здійснюють повернення даних, можна настроїти так, щоб вони повертали при тестуванні одні і ті ж правдоподібні дані. Вони можуть емулювати помилки так, щоб код обробки помилок міг бути ретельно протестований. Іншими прикладами fake-служб, корисними при розробці через тестування, можуть бути: служба кодування, яка не кодує дані, генератор випадкових чисел, який завжди видає одиницю. Fake - або mock- реалізації є прикладами впровадження залежності (dependency injection). Використання fake - і mock- об'єктів для представлення зовнішнього світу призводить до того, що справжня база даних і інший зовнішній код не будуть протестовані в результаті процесу розробки через тестування. Щоб уникнути помилок, потрібні тести реальних реалізацій інтерфейсів, описаних вище. Ці тести можуть бути відокремлені від інших модульних тестів і реально є інтеграційними тестами. Їх необхідно менше, ніж модульних, і вони можуть запускатися рідше. Проте, найчастіше вони реалізуються використовуючи ті ж бібліотеки для тестування (testing framework), що і модульні тести. Інтеграційні тести, які змінюють дані у базі даних, повинні повертати стан бази даних до того, яке було до запуску тесту, навіть якщо тест не пройшов. Для цього часто застосовується наступна техніка: Метод TearDown, присутній у більшості бібліотек для тестування (test frameworks). try...catch...finally структури обробки виключень, там де вони доступні. Транзакції баз даних. Створення знімка (snapshot) бази даних перед запуском тестів і відкат до нього після закінчення тестування. Скидання бази даних в чистий стан перед тестом, а не після них. Це може бути зручно, якщо цікаво подивитися стан бази даних, що залишився після не пройденого теста. Існують бібліотеки Moq, jMock, NMock, EasyMock, Typemock, jMockit, Unitils, Mockito, Mockachino, PowerMock or Rhino Mocks, призначені спростити процес створення mock-об'єктів. |