Среда, 27.11.2024
Ukrainiancontet.at.ua
Меню сайта
Категории раздела
Програмування [35]
Мови веб-програмування [5]
О компании [0]
Новости игры
Статистика

Онлайн всего: 3
Гостей: 3
Пользователей: 0
Главная » 2015 » Октябрь » 26 » Розробка через тестування
20:18
Розробка через тестування
Розробка через тестування (англ. test-driven development, TDD) — техніка розробки програмного забезпечення, яка ґрунтується на повторенні дуже коротких циклів розробки: спочатку пишеться тест, що покриває бажана зміна, потім пишеться код, який дозволить пройти тест, і під кінець проводиться рефакторинг нового коду до відповідних стандартів. Кент Бек, який вважається винахідником цієї техніки, стверджував у 2003 році, що розробка через тестування заохочує простий дизайн і вселяє впевненість (англ. inspires confidence)[].

У 1999 році при своїй появі розробка через тестування була тісно пов'язана з концепцією «спочатку тест» (англ. test-first), застосовуваної в екстремальному програмуванні[], однак пізніше виділилася як незалежна методологія.[].

Тест — це процедура, яка дозволяє або підтвердити, або спростувати працездатність коду. Коли програміст перевіряє працездатність розробленого ним коду, він виконує тестування вручну.

Розробка через тестування вимагає від розробника створення автоматизованих модульних тестів, що визначають вимоги до коду безпосередньо перед написанням самого коду. Тест містить перевірки умов, які можуть виконуватися або ні. Коли вони виконуються, кажуть, що тест пройдено. Проходження тесту підтверджує поведінка, передбачуване програмістом. Розробники часто користуються бібліотеками для тестування (англ. testing frameworks) для створення та автоматизації запуску наборів тестів. На практиці модульні тести покривають критичні і нетривіальні ділянки коду. Це може бути код, який піддається частим змінам, код, від роботи якого залежить працездатність великої кількості іншого коду, або код з великою кількістю залежностей.

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

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

Зрозуміло, до тестів застосовуються ті ж вимоги стандартів кодування, що і до основного коду.

Наведена послідовність дій заснована на книзі Кента Бека «Розробка через тестування: на прикладі» (англ. Test Driven Development: By Example).

При розробці через тестування, додавання нової функціональності (англ. feature) в програму починається з написання тесту. Неминуче цей тест не буде проходити, оскільки відповідний код ще не написаний. (Якщо ж написаний тест пройшов, це означає, що або запропонована «нова» функціональність вже існує, або тест має недоліки.) Щоб написати тест, розробник повинен чітко розуміти що пред'являються до нової можливості вимоги. Для цього розглядаються можливі сценарії використання і користувальницькі історії. Нові вимоги можуть також спричинити зміну існуючих тестів. Це відрізняє розроблення через тестування від технік, коли тести пишуться після того, як код написаний: вона змушує розробника сфокусуватися на вимогах до написання коду — тонка, але важлива відмінність.

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

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

Важливо писати код, призначений саме для проходження тесту. Не слід додавати зайвою і, відповідно, не досліджуваної функціональності.

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

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

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

Розробка через тестування тісно пов'язана з такими принципами, як «роби простіше, дурник» (англ. keep it simple, stupid, KISS) і «вам це не знадобиться» (англ. 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) — тести, що перевіряють функціональність програми на відповідність вимогам замовника. Приймальні тести проходять на стороні замовника. Це допомагає йому бути впевненим в тому, що він отримає всю необхідну функціональність.

Дослідження 2005 року показало, що використання розробки через тестування передбачає написання більшої кількості тестів, у свою чергу, програмісти, що пишуть більше тестів, схильні бути більш продуктивними.[5] Гіпотези пов'язують якість коду з TDD були непереконливі.[6]

Програмісти, які використовують TDD на нових проектах, відзначають, що вони рідше відчувають необхідність використовувати відладчик. Якщо деякі з тестів несподівано перестають проходити, відкат до останньої версії, яка проходить всі тести, може бути більш продуктивним, ніж налагодження.[7]

Розробка через тестування пропонує більше, ніж просто перевірку коректності, вона також впливає на дизайн програми. Спочатку сфокусувавшись на тестах, простіше уявити, яка функціональність необхідна користувачеві. Таким чином, розробник продумує деталі інтерфейсу до реалізації. Тести змушують робити свій код більш пристосованим для тестування. Наприклад, відмовлятися від глобальних змінних, одинаків (singletons), робити класи менш пов'язаними і легкими для використання. Сильно пов'язаний код або код, який вимагає складної ініціалізації, буде значно важче протестувати. Модульне тестування сприяє формуванню чітких і невеликих інтерфейсів. Кожен клас буде виконувати певну роль, як правило невелику. Як наслідок, залежності між класами будуть знижуватися, а зачеплення підвищуватися. Контрактне програмування (англ. design by contract) доповнює тестування, формуючи необхідні вимоги через затвердження (англ. assertions).

Незважаючи на те, що при розробці через тестування потрібно написати більшу кількість коду, загальний час, витрачений на розробку, зазвичай виявляється менше. Тести захищають від помилок. Тому час, що витрачається на налагодження, знижується багаторазово.[8] Велика кількість тестів допомагає зменшити кількість помилок в коді. Усунення дефектів на більш ранньому етапі розробки, перешкоджає появі хронічних і дорогих помилок, що призводять до тривалої та виснажливої налагодження надалі.

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

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

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

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

Існують завдання, які неможливо (принаймні, на поточний момент) вирішити тільки за допомогою тестів. Зокрема, TDD не дозволяє механічно продемонструвати адекватність розробленого коду в області безпеки даних і взаємодії між процесами. Безумовно, безпека заснована на коді, в якому не повинно бути дефектів, проте вона заснована також на участь людини в процедурах захисту даних. Тонкі проблеми, що виникають у сфері взаємодії між процесами, неможливо з упевненістю відтворити, просто запустивши певний код.
Розроблення через тестування складно застосовувати в тих випадках, коли для проходження тестування необхідно функціональних тестів. Прикладами може бути: розробка інтерфейсів користувача, програм, що працюють з базами даних, а також того, що залежить від специфічної конфігурації мережі. Розробка через тестування не передбачає великого обсягу роботи з тестування такого роду речей. Вона зосереджується на тестуванні окремо взятих модулів, використовуючи mock-об'єкти для подання зовнішнього світу.
Потрібно більше часу на розробку та підтримку, схвалення з боку керівництва дуже важливо. Якщо в організації немає впевненості в тому, що розробка через тестування поліпшить якість продукту, то час, витрачений на написання тестів, може розглядатися як витрачений даремно.[9]
Модульні тести, що створюються при розробці через тестування, зазвичай пишуться тими ж, хто пише досліджуваний код. Якщо розробник неправильно витлумачив вимоги до додатку, і тест, і досліджуваний модуль будуть містити помилку.
Велика кількість використовуваних тестів можуть створити хибне відчуття надійності, що приводить до меншої кількості дій по контролю якості.
Тести самі по собі є джерелом накладних витрат. Погано написані тести, наприклад, містять жорстко вшиті рядки з повідомленнями про помилки або схильні до помилок, дороги при підтримці. Щоб спростити підтримку тестів слід повторно використовувати повідомлення про помилки з досліджуваного коду.
Рівень покриття тестами, отриманий в результаті розробки через тестування, не може бути легко отриманий згодом. Вихідні тести стають все більш цінними з плином часу. Якщо невдалі архітектура, дизайн або стратегія тестування призводять до великої кількості непройдених тестів, важливо їх все виправити в індивідуальному порядку. Просте видалення, відключення або поспішна зміна їх може призвести до необнаруживаемым прогалин у покритті тестами.

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

З коду тесту може не бути доступу до приватних (англ. private) полів і методів. Тому при модульному тестуванні може знадобитися додаткова робота. В Java, розробник може використовувати відображення (англ. reflection), щоб звертатися до полями, позначеними як приватні.[] Модульні тести можна реалізувати у внутрішніх класах, щоб вони мали доступ до членів зовнішнього класу. В .NET Framework можуть застосовуватися загальні класи (англ. partial classes) для доступу до тесту приватним полів і методів.

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

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

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

Коли розробляється код використовує бази даних, веб-сервіси або інші зовнішні процеси, має сенс виділити покривається тестуванням частина. Це робиться в два кроки:
Скрізь, де потрібен доступ до зовнішніх ресурсів, повинен бути оголошений інтерфейс, через який цей доступ буде здійснюватися. См. принцип інверсії залежностей (англ. dependency inversion) для обговорення переваг цього підходу незалежно від TDD.
Інтерфейс повинен мати дві реалізації. Перша, власне надає доступ до ресурсу, і друга, є fake - або mock-об'єктом. Все, що роблять fake-об'єкти, це додають повідомлення виду «Об'єкт person збережений» в лог, щоб потім перевірити правильність поведінки. Mock-об'єкти відрізняються від fake - тим, що самі містять твердження (англ. assertion), перевіряючі поведінку досліджуваного коду. Методи fake - і mock-об'єктів, які повертають дані, можна налаштувати так, щоб вони повертали при тестуванні одні і ті ж правдоподібні дані. Вони можуть емулювати помилки так, щоб код обробки помилок міг бути ретельно протестований. Іншими прикладами fake-служб, корисними при розробці через тестування, можуть бути: служба кодування, яка не кодує дані, генератор випадкових чисел, який завжди видає одиницю. Fake - або mock-реалізації є прикладами впровадження залежності (англ. dependency injection).

Використання fake - і mock-об'єктів для подання зовнішнього світу призводить до того, що справжня база даних та іншої зовнішній код не будуть протестовані в результаті процесу розробки через тестування. Щоб уникнути помилок, необхідні тести реальних реалізацій інтерфейсів, описаних вище. Ці тести можуть бути відділені від інших модульних тестів і реально є інтеграційними тестами. Їх необхідно менше, ніж модульних, і вони можуть запускатися рідше. Тим не менш, частіше за все вони реалізуються використовуючи ті ж бібліотеки для тестування (англ. testing framework), що і модульні тести.

Інтеграційні тести, які змінюють дані в базі даних, повинні відкочувати стані бази даних до того, яке було до запуску тесту, навіть якщо тест не пройшов. Для цього часто застосовуються наступні техніки:
Метод TearDown, присутній у більшості бібліотек для тестування.
try...catch...finally структури обробки винятків, там де вони доступні.
Транзакції баз даних.
Створення знімка (англ. snapshot) бази даних перед запуском тестів і відкат до нього після закінчення тестування.
Скидання бази даних в чисте стан перед тестом, а не після них. Це може бути зручно, якщо цікаво подивитися стан бази даних, що залишився після не пройшов тесту.

Існують бібліотеки Moq, jMock, NMock, EasyMock, Typemock, jMockit, Unitils, Mockito, Mockachino, PowerMock або Rhino Mocks, а також sinon для JavaScript призначені спростити процес створення mock-об'єктів.
Категория: Програмування | Просмотров: 638 | Добавил: Adminn | Теги: Розробка через тестування | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Вход на сайт

Поиск
Интернет
Здоровье
Афиша
Ситуация на восток
Религия
Архив записей
Каталог сайтов Всего.RU
Рейтинг@Mail.ru
Copyright Ukrainiancontet.at.ua © 2024
uCozЯндекс.Метрика