Керована тестами розробка: відмінності між версіями

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
[неперевірена версія][неперевірена версія]
Вилучено вміст Додано вміст
Виправлена орфографічна помилка
 
(Не показано 34 проміжні версії 22 користувачів)
Рядок 1: Рядок 1:
{{Шаблон:ТРПЗ}}
{{ТРПЗ}}
{{Otheruses|TDD}}
'''Розробка через тестування''' ({{lang-en|Test-driven development (TDD)}}) — [[технологія розробки програмного забезпечення]], яка використовує короткі ітерації розробки, що починаються з попереднього написання тестів, які визначають необхідні покращення або нові функції. Кожна ітерація має на меті розробити код, який пройде ці тести. Нарешті, програміст або група вдосконалюють код для погодження змін. Один із ключових моментів TDD полягає у тому, що підготовка тестів перед написанням самого коду пришвидшує процес внесення змін. Варто зауважити, що розробка через тестування є методологією розробки програмного забезпечення, а не його [[Тестування програмного забезпечення|тестування]].
'''Керована тестами розробка (КТР),''' '''Розробка через тестування''' ({{lang-en|Test-driven development (TDD)}}) — [[технологія розробки програмного забезпечення]], яка використовує короткі ітерації розробки, що починаються з попереднього написання тестів, які визначають необхідні покращення або нові функції. Кожна ітерація має на меті розробити код, який пройде ці тести. Нарешті, програміст або група вдосконалюють код для погодження змін. Один із ключових моментів TDD полягає у тому, що підготовка тестів перед написанням самого коду пришвидшує процес внесення змін. Варто зауважити, що керована тестами розробка є методологією '''розробки''' програмного забезпечення, а не його [[Тестування програмного забезпечення|тестування]].


Test-Driven Development відноситься до концепції [[Екстремальне програмування|екстремального програмування]], яка стверджує, що першим ділом потрібно писати тести, а вже потім код, яка веде свій початок з [[1999]] року,<ref name="Cworld92">
Test-Driven Development відноситься до концепції [[Екстремальне програмування|екстремального програмування]], яка стверджує, що спершу потрібно писати тести, а вже потім код, яка веде свій початок з [[1999]] року,<ref name="Cworld92">"Extreme Programming", ''Computerworld'' (online),
"Extreme Programming", ''Computerworld'' (online),
December 2001, webpage:
December 2001, webpage:
[http://www.computerworld.com/softwaretopics/software/appdev/story/0,10801,66192,00.html Computerworld-appdev-92].
[http://www.computerworld.com/softwaretopics/software/appdev/story/0,10801,66192,00.html Computerworld-appdev-92] {{Webarchive|url=https://web.archive.org/web/20110605060209/http://www.computerworld.com/s/article/66192/Extreme_Programming?taxonomyId=063 |date=2011-06-05 }}.</ref> однак, останнім часом спостерігається загальніший інтерес до даної методології.<ref name=Newkirk>Newkirk, JW and Vorontsov, AA. ''Test-Driven Development in Microsoft .NET'', Microsoft Press, 2004.</ref>

</ref> однак, останнім часом спостерігається загальніший інтерес до даної методології.<ref name=Newkirk>Newkirk, JW and Vorontsov, AA. ''Test-Driven Development in Microsoft .NET'', Microsoft Press, 2004.</ref>
Програмісти також використовують дану методологію для вдосконалення і [[зневадження]] [[сирцевий код|сирцевого коду]], раніше написаного з використанням інших методологій розробки.<ref name=Feathers>Feathers, M. Working Effectively with Legacy Code, Prentice Hall, 2004</ref>


Програмісти також використовують дану методологію для вдосконалення і зневадження коду, раніше написаного з використанням інших методологій розробки.<ref name=Feathers>Feathers, M. Working Effectively with Legacy Code, Prentice Hall, 2004</ref>


[[Файл:Test-driven development_uk.png|frame|center|Графічна репрезентація циклу розробки]]


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


Середовище розробки повинно швидко реагувати на невеликі модифікації коду. Архітектура програми повинна базуватися на використанні безлічі [[Пов'язаність (програмування)|сильно пов’язаних]] компонентів, які [[Зв'язність (програмування)|слабо залежать]] одне від одного , завдяки чому тестування коду спрощується.
Середовище розробки повинно швидко реагувати на невеликі модифікації коду. Архітектура програми повинна базуватися на використанні безлічі [[Пов'язаність (програмування)|сильно пов’язаних]] компонентів, які [[Зв'язність (програмування)|слабо залежать]] одне від одного , завдяки чому тестування коду спрощується.
Рядок 20: Рядок 19:


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

== Три закони TDD ==
# '''Не можна писати жодного [[Сирцевий код|вихідного коду]], доки спершу не написано падаючого [[Юніт-тестування|юніт-тесту]] ('''{{lang-en|unit test}}''').'''
# '''Не можна писати більше [[Юніт-тестування|юніт-тесту]] ніж необхідно для падіння (непроходження тесту). Помилка компіляції - це також падіння ('''{{lang-en|failing}}''').'''
# '''Не можна писати більше [[Сирцевий код|вихідного коду]], ніж необхідно для проходження вдалого [[Юніт-тестування|юніт-тесту]].'''


== Цикл розробки за методологією TDD ==
== Цикл розробки за методологією TDD ==
[[Файл:Test-driven development uk.png|міні|праворуч|450пкс|Графічна репрезентація циклу розробки TDD]]
Усе нижчевказане базується на книзі ''[[Test-Driven Development by Example]]'',<ref name=Beck>Beck, K. Test-Driven Development by Example, Addison Wesley, 2003</ref> котру багато хто вважає канонічним текстом про дану концепцію у її сучасному вигляді.
Усе нижчевказане базується на книзі ''[[Test-Driven Development by Example]]'',<ref name=Beck>Beck, K. Test-Driven Development by Example, Addison Wesley, 2003</ref> котру багато хто вважає канонічним текстом про дану концепцію у її сучасному вигляді.


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


=== 2. Запустити усі тести, і подивитись, чи вони пройшли ===
=== 2. Запустити усі тести, і подивитись, чи вони пройшли ===
Рядок 41: Рядок 46:


=== 5. Удосконалити код ===
=== 5. Удосконалити код ===
Тепер код при необхідності може бути "вичищений". Шляхом перезапуску усіх тестів, розробник може впевнитись у тому, що [[рефакторинг]] коду не порушив його відповідність специфікаціям. Концепція вилучення з коду дублікатів є важливим аспектом будь якого
Тепер код при необхідності може бути "вичищений". Шляхом перезапуску усіх тестів, розробник може впевнитись у тому, що [[рефакторинг]] коду не порушив його відповідність специфікаціям. Концепція вилучення з коду дублікатів є важливим аспектом будь-якого дизайну програмного забезпечення.
дизайну програмного забезпечення.


== Стиль розробки ==
== Стиль розробки ==
Розробка через тестування тісно пов'язана з такими принципами як «роби простіше, дурник» ({{lang-en | keep it simple, stupid, [[KISS (принцип) | KISS]]}}) і «вам це не знадобиться» ({{lang-en | you ain't gonna need it, [[Принцип YAGNI | YAGNI]]}}). Дизайн може бути чистіше і ясніше при написанні лише того коду, який необхідний для проходження тесту. <ref Name=Beck/> Кент Бек також пропонує принцип «підроби, поки не зробиш» ({{lang-en | fake it till you make it}}).
Керована тестами розробка тісно пов'язана з такими принципами як «роби простіше, дурник» ({{lang-en | keep it simple, stupid, [[KISS (принцип)|KISS]]}}) і «вам це не знадобиться» ({{lang-en | you ain't gonna need it, [[Принцип YAGNI|YAGNI]]}}). Дизайн може бути чистіше і ясніше при написанні лише того коду, який необхідний для проходження тесту. <ref Name=Beck/> Кент Бек також пропонує принцип «підроби, поки не зробиш» ({{lang-en | fake it till you make it}}).
Тести повинні писатися для тестованої функціональності. Вважається, що це має дві переваги. Це допомагає переконатися, що додаток придатний для тестування, оскільки розробнику доведеться з самого початку обдумати те, як додаток буде тестуватися. Це також сприяє тому, що тестами буде покрита вся функціональність. Коли функціональність пишеться до тестів, розробники та організації схильні переходити до реалізації наступної функціональності, ще не протестувавши існуючу.


Тести повинні писатися для тестованої функціональності. Вважається, що це має дві переваги. Це допомагає переконатися, що [[застосунок]] придатний для тестування, оскільки розробнику доведеться з самого початку обдумати те, як застосунок буде тестуватися. Це також сприяє тому, що тестами буде покрита вся функціональність. Коли функціональність пишеться до тестів, розробники та організації схильні переходити до реалізації наступної функціональності, ще не протестувавши існуючу.
Ідея перевіряти, що тільки написаний тест не проходить, допомагає переконатися, що тест реально щось перевіряє. Тільки після цієї перевірки слід приступати до реалізації нової функціональності. Цей прийом, відомий як «червоний / зелений / рефакторінг», називають «мантрою розробки через тестування». Під червоним тут розуміють тести, які не пройшли, а під зеленим - які пройшли тестування.


Ідея перевіряти, що тільки написаний тест не проходить, допомагає переконатися, що тест реально щось перевіряє. Тільки після цієї перевірки слід приступати до реалізації нової функціональності. Цей прийом, відомий як «червоний / зелений / рефакторинг», називають «мантрою розробки через тестування». Під червоним тут розуміють тести, які не пройшли, а під зеленим - які пройшли тестування.
Відпрацьовані практики розробки через тестування призвели до створення техніки «розробка через прийомочне тестування» ({{lang-en | Acceptance Test-driven development, ATDD}}), в якому критерії описані замовником автоматизуються в приймальні тести, що використовуються потім в звичайному процесі розробки через модульне тестування ({{lang-en | unit test-driven development, UTDD}}). <ref name="Koskela"> Koskela, L. «Test Driven: TDD and Acceptance TDD for Java Developers», Manning Publications, 2007 </ref> Цей процес дозволяє гарантувати, що додаток задовольняє сформульованим вимогам. При розробці через прийомочне тестування, команда розробників сконцентрована на чіткій задачі: задовольнити приймальні тести, які відображають відповідні вимоги користувача.


Відпрацьовані практики розробки через тестування призвели до створення техніки «Керована приймальними тестами розробка» ({{lang-en | Acceptance Test-driven development, ATDD}}), в якому критерії описані замовником автоматизуються в приймальні тести, що використовуються потім в звичайному процесі керованої модульними тестами розробки ({{lang-en | unit test-driven development, UTDD}}). <ref name="Koskela"> Koskela, L. «Test Driven: TDD and Acceptance TDD for Java Developers», Manning Publications, 2007 </ref> Цей процес дозволяє гарантувати, що застосунок задовольняє сформульованим вимогам. При керованій приймальними тестами розробці, команда розробників сконцентрована на чіткій задачі: задовольнити приймальні тести, які відображають відповідні вимоги користувача.
Прийомочні (функціональні) тести ({{lang-en | customer tests, acceptance tests}}) - тести, перевіряючі функціональність програми на відповідність вимогам замовника. Прийомочні тести проходять на стороні замовника. Це допомагає йому бути впевненим у тому, що він отримає всю необхідну функціональність.

== Переваги ==
Дослідження 2005 року показало, що використання розробки через тестування передбачає написання більшої кількості тестів; у свою чергу, програмісти, які пишуть більше тестів, схильні бути більш продуктивними. <ref>{{cite web
| url = http://iit-iti.nrc-cnrc.gc.ca/publications/nrc-47445_e.html
| title = On the Effectiveness of Test-first Approach to Programming
| accessdate = 2008-01-14
| last = Erdogmus
| first = Hakan
| coauthors = Morisio, Torchiano
| publisher = Proceedings of the IEEE Transactions on Software Engineering, 31(1). January 2005. (NRC 47445)
| quote = We found that test-first students on average wrote more tests and, in turn, students who wrote more tests tended to be more productive.
| archiveurl = http://www.webcitation.org/61G4jTMDZ
| archivedate = 2011-08-28
}}</ref> Гіпотези, які зв'язують якість коду з TDD були непереконливими.<ref>{{cite web
| url = http://theruntime.com/blogs/jacob/archive/2008/01/22/tdd-proven-effective-or-is-it.aspx
| title = TDD Proven Effective! Or is it?
| accessdate = 2008-02-21
| last = Proffitt
| first = Jacob
| quote = So TDD's relationship to quality is problematic at best. Its relationship to productivity is more interesting. I hope there's a follow-up study because the productivity numbers simply don't add up very well to me. There is an undeniable correlation between productivity and the number of tests, but that correlation is actually stronger in the non-TDD group (which had a single outlier compared to roughly half of the TDD group being outside the 95% band).
| archiveurl = http://www.webcitation.org/61G4k4frE
| archivedate = 2011-08-28
}}</ref>

Програмісти, які використовують TDD на нових проектах, відзначають, що вони рідше відчувають необхідність використовувати відладчик. Якщо деякі з тестів несподівано перестають проходити, відкат до останньої версії, яка проходить всі тести, може бути більш продуктивним, ніж налагодження. <ref>{{cite web
| url = http://www.gamesfromwithin.com/articles/0502/000073.html
| title = Stepping Through the Looking Glass: Test-Driven Game Development (Part 1)
| accessdate = 2007-11-01
| last = Llopis
| first = Noel
| date = 2005-02-20
| publisher = Games from Within
| quote = Comparing [TDD] to the non-test-driven development approach, you're replacing all the mental checking and debugger stepping with code that verifies that your program does exactly what you intended it to do.
| deadurl = 404
}}</ref>

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

Незважаючи на те, що при розробці через тестування потрібно написати більшу кількість коду, загальний час, витрачений на розробку, зазвичай виявляється менше. Тести захищають від помилок. Тому час, що витрачається на налагодження, зменшується в рази. <ref>{{cite web
| url = http://www.ipd.uka.de/mitarbeiter/muellerm/publications/edser03.pdf
| title = About the Return on Investment of Test-Driven Development
| accessdate = 2007-11-01
| author =
| last = Müller
| first = Matthias M.
| coauthors = Padberg, Frank
| format = PDF
| publisher = Universität Karlsruhe, Germany
| pages = 6
| archiveurl = http://www.webcitation.org/61G4kow2C
| archivedate = 2011-08-28
}}</ref> Велика кількість тестів допомагає зменшити кількість помилок в коді. Усунення дефектів на більш ранньому етапі розробки перешкоджає появі хронічних помилок, що призводять до тривалого та виснажливого ​​налагодження в майбутньому.

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

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

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

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


== Слабкі місця ==
* Головним недоліком TDD є те, що до нього тяжко звикнути.
* Існують завдання, які неможливо (принаймні, на поточний момент) вирішити тільки за допомогою тестів. Зокрема, TDD не дозволяє механічно продемонструвати адекватність розробленого коду в області безпеки даних і взаємодії між процесами. Безумовно, безпека заснована на коді, в якому не повинно бути дефектів, проте вона заснована також на участі людини у процедурах захисту даних. Тонкі проблеми, що виникають у сфері взаємодії між процесами, неможливо з упевненістю відтворити, просто запустивши деякий код.
* Розробку через тестування складно застосовувати в тих випадках, коли для тестування необхідно проходження функціональних тестів. Прикладами може бути: розробка інтерфейсів користувача, програм, що працюють з базами даних, а також того, що залежить від специфічної конфігурації мережі. Розробка через тестування не передбачає великого обсягу роботи з тестування такого роду речей. Вона зосереджується на тестуванні окремо взятих модулів, використовуючи mock-об'єкти для представлення зовнішнього світу.
* Потрібно більше часу на розробку і підтримку, а схвалення з боку керівництва дуже важливо. Якщо в організації немає впевненості в тому, що розробка через тестування поліпшить якість продукту, то час, витрачений на написання тестів, може розглядатися як витрачений даремно. <ref>{{cite web
| url = http://people.apache.org/~stevel/slides/testing.pdf
| title = Testing
| last = Loughran
| first = Steve
| date = November 6th, 2006
| publisher = HP Laboratories
| accessdate = 2009-08-12
| format = PDF
| archiveurl = http://www.webcitation.org/61G4lOhjb
| archivedate = 2011-08-28
}}</ref>
* Модульні тести, створювані при розробці через тестування, звичайно пишуться тими ж, хто пише тестований код. Якщо розробник неправильно витлумачив вимоги до додатка, і тест, і тестований модуль будуть містити помилку.
* Велика кількість використовуваних тестів можуть створити помилкове відчуття надійності, що приводить до меншої кількості дій з контролю якості.
* Тести самі по собі є джерелом накладних витрат. Погано написані тести, наприклад, містять жорстко вшиті рядки з повідомленнями про помилки або схильні до помилок. Щоб спростити підтримку тестів слід повторно використовувати повідомлення про помилки з тестованого коду.
* Рівень покриття тестами, що отримується в результаті розробки через тестування, не може бути легко отриманий згодом. Вихідні тести стають все більш цінними з плином часу. Якщо невдалі архітектура, дизайн або стратегія тестування призводять до великої кількості непройдених тестів, важливо їх всі виправити в індивідуальному порядку. Просте видалення, відключення або поспішна зміна їх може призвести до прогалин у покритті тестами.


Приймальні (функціональні) тести ({{lang-en | customer tests, acceptance tests}}) - тести, що перевіряють функціональність програми на відповідність вимогам замовника. Приймальні тести проходять на стороні замовника. Це допомагає йому бути впевненим у тому, що він отримає всю необхідну функціональність.


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


З коду тесту може не бути доступу до private полів і методів. Тому при модульному тестуванні може знадобитися додаткова робота. В [[Java]], розробник може використовувати [[Відображення (програмування) | відображення]] ({{lang-en | reflection}}), щоб звертатися до полів, позначеним як private. <ref>{{cite web
З коду тесту може не бути доступу до private полів і методів. Тому при модульному тестуванні може знадобитися додаткова робота. В [[Java]], розробник може використовувати [[Відображення (програмування)|відображення]] ({{lang-en | reflection}}), щоб звертатися до полів, позначеним як private. <ref>{{cite web
| title = Subverting Java Access Protection for Unit Testing
|title = Subverting Java Access Protection for Unit Testing
| url = http://www.onjava.com/pub/a/onjava/2003/11/12/reflection.html
|url = http://www.onjava.com/pub/a/onjava/2003/11/12/reflection.html
| last = Burton
|last = Burton
| first = Ross
|first = Ross
| date = 11/12/2003
|date = 11/12/2003
| publisher = O'Reilly Media, Inc.
|publisher = O'Reilly Media, Inc.
| accessdate = 2009-08-12
|accessdate = 2009-08-12
| archiveurl = http://www.webcitation.org/61G4lu7Yy
|archiveurl = https://web.archive.org/web/20090727080406/http://onjava.com/pub/a/onjava/2003/11/12/reflection.html
| archivedate = 2011-08-28
|archivedate = 2009-07-27
|deadurl = yes
}}</ref> Модульні тести можна реалізувати у внутрішніх класах, щоб вони мали доступ до членів зовнішнього класу. В [[. NET Framework]] можуть застосовуватися колективні класи ({{lang-en | partial classes}}) для доступу з теста до private полів і методів.
}}</ref> Модульні тести можна реалізувати у внутрішніх класах, щоб вони мали доступ до членів зовнішнього класу. В [[. NET Framework]] можуть застосовуватися колективні класи ({{lang-en | partial classes}}) для доступу з теста до private полів і методів.


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


Не існує єдиної думки серед програмістів, які застосовують розробку через тестування, про те, наскільки це осмислено тестувати private- і protected- методи і дані. Одні переконані, що достатньо протестувати будь-який клас тільки через його public-інтерфейс, оскільки private-члени - це всього лише деталь реалізації, яка може змінюватися, і її зміни не повинні відображатися на наборі тестів. Інші стверджують, що важливі аспекти функціональності можуть бути реалізовані в private-методах і тестування їх неявно через public-інтерфейс лише ускладнить ситуацію: модульне тестування передбачає тестування найменших можливих модулів функціональності. <ref>{{cite web
Не існує єдиної думки серед програмістів, які застосовують розробку через тестування, про те, наскільки це осмислено тестувати private- і protected- методи і дані. Одні переконані, що достатньо протестувати будь-який клас тільки через його public-інтерфейс, оскільки private-члени - це всього лише деталь реалізації, яка може змінюватися, і її зміни не повинні відображатися на наборі тестів. Інші стверджують, що важливі аспекти функціональності можуть бути реалізовані в private-методах і тестування їх неявно через public-інтерфейс лише ускладнить ситуацію: модульне тестування передбачає тестування найменших можливих модулів функціональності. <ref>{{cite web
| url = http://blogs.msdn.com/jamesnewkirk/archive/2004/06/07/150361.aspx
|url = http://blogs.msdn.com/jamesnewkirk/archive/2004/06/07/150361.aspx
| title = Testing Private Methods/Member Variables - Should you or shouldn't you
|title = Testing Private Methods/Member Variables - Should you or shouldn't you
| last = Newkirk
|last = Newkirk
| first = James
|first = James
| date = 7 June 2004
|date = 7 червня 2004
| publisher = Microsoft Corporation
|publisher = Microsoft Corporation
| accessdate = 2009-08-12
|accessdate = 2009-08-12
|archiveurl = https://web.archive.org/web/20090630081941/http://blogs.msdn.com/jamesnewkirk/archive/2004/06/07/150361.aspx
| archiveurl = http://www.webcitation.org/61G4mlTOE
| archivedate = 2011-08-28
|archivedate = 2009-06-30
|deadurl = yes
}}</ref><ref>{{cite web
}}</ref><ref>{{cite web
| url = http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx
|url = http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx
| title = How to Test Private and Protected methods in .NET
|title = How to Test Private and Protected methods in .NET
| last = Stall
|last = Stall
| first = Tim
|first = Tim
| date = 1 Mar 2005
|date = 1 березня 2005
| publisher = CodeProject
|publisher = CodeProject
| accessdate = 2009-08-12
|accessdate = 2009-08-12
| archiveurl = http://www.webcitation.org/61G4nciiK
|archiveurl = https://web.archive.org/web/20090903004928/http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx
| archivedate = 2011-08-28
|archivedate = 2009-09-03
|deadurl = yes
}}</ref>
}}</ref>

== [[Пріоритет трансформацій]] ==
Згідно концепції TDD, тести "ведуть" розробника до певної реалізації. Проходження впалого тесту досягається завдяки '''трансформації''' вихідного коду. Часом, можна трансформувати код таким чином, що подальше тестування буде вкрай ускладнене і вимагатиме переосмислення реалізації значної частини або, навіть, усього юніта (функції, методу класу). Тобто при невірному використанні TDD може завести недосвідченого розробника в "глухий кут", або привести до неоптимальної реалізації алгоритму. Для допомоги розробникам було запропоновано принцип '''[[Пріоритет трансформацій|пріоритету трансформацій]]'''. Він полягає в тому, що потрібно завжди віддавати перевагу простішим трансформаціям над складнішими. Дотримання цього принципу може допомогти уникати "глухих кутів" та розробляти оптимальніші алгоритми виконання.




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


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


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


Використання fake-і mock-об'єктів для представлення зовнішнього світу призводить до того, що справжня база даних та інший зовнішній код не будуть протестовані в результаті процесу розробки через тестування. Щоб уникнути помилок, необхідні тести реальних реалізацій інтерфейсів, описаних вище. Ці тести можуть бути відокремлені від інших модульних тестів і реально є інтеграційними тестами. Їх необхідно менше, ніж модульних, і вони можуть запускатися рідше. Проте, найчастіше вони реалізуються використовуючи ті ж бібліотеки для тестування ({{lang-en | testing framework}}), що і модульні тести.
Використання fake-і mock-об'єктів для представлення зовнішнього світу призводить до того, що справжня база даних та інший зовнішній код не будуть протестовані в результаті процесу розробки через тестування. Щоб уникнути помилок, необхідні тести реальних реалізацій інтерфейсів, описаних вище. Ці тести можуть бути відокремлені від інших модульних тестів і реально є інтеграційними тестами. Їх необхідно менше, ніж модульних, і вони можуть запускатися рідше. Проте, найчастіше вони реалізуються використовуючи ті ж бібліотеки для тестування ({{lang-en | testing framework}}), що і модульні тести.
Рядок 190: Рядок 117:
Інтеграційні тести, які змінюють дані в базі даних, повинні відкочувати стани бази даних до того, яке було до запуску тесту, навіть якщо тест не пройшов. Для цього часто застосовуються такі техніки:
Інтеграційні тести, які змінюють дані в базі даних, повинні відкочувати стани бази даних до того, яке було до запуску тесту, навіть якщо тест не пройшов. Для цього часто застосовуються такі техніки:
* Метод <code> TearDown </code>, присутній в більшості бібліотек для тестування.
* Метод <code> TearDown </code>, присутній в більшості бібліотек для тестування.
* <code> Try ... catch ... finally </code> структури обробки виключень, там де вони доступні.
* <code> Try ... catch ... finally </code> структури обробки винятків, там де вони доступні.
* Транзакції баз даних.
* Транзакції баз даних.
* Створення знімка ({{lang-en | snapshot}}) бази даних перед запуском тестів і відкат до нього після закінчення тестування.
* Створення знімка ({{lang-en | snapshot}}) бази даних перед запуском тестів і відкат до нього після закінчення тестування.
Рядок 197: Рядок 124:
Існують бібліотеки Moq, jMock, NMock, EasyMock, Typemock, jMockit, Unitils, Mockito, Mockachino, PowerMock or Rhino Mocks, призначені спростити процес створення mock-об'єктів.
Існують бібліотеки Moq, jMock, NMock, EasyMock, Typemock, jMockit, Unitils, Mockito, Mockachino, PowerMock or Rhino Mocks, призначені спростити процес створення mock-об'єктів.


== Переваги ==
Дослідження 2005 року показало, що використання розробки через тестування передбачає написання більшої кількості тестів; у свою чергу, програмісти, які пишуть більше тестів, схильні бути більш продуктивними. <ref>{{cite web
|url = http://iit-iti.nrc-cnrc.gc.ca/publications/nrc-47445_e.html
|title = On the Effectiveness of Test-first Approach to Programming
|accessdate = 2008-01-14
|last = Erdogmus
|first = Hakan
|coauthors = Morisio, Torchiano
|publisher = Proceedings of the IEEE Transactions on Software Engineering, 31(1). January 2005. (NRC 47445)
|quote = We found that test-first students on average wrote more tests and, in turn, students who wrote more tests tended to be more productive.
|archiveurl = https://www.webcitation.org/61G4jTMDZ?url=http://www.nrc-cnrc.gc.ca/eng/ibp/iit.html
|archivedate = 2011-08-27
|deadurl = yes
}}</ref> Гіпотези, які зв'язують якість коду з TDD були непереконливими.<ref>
{{cite web
|url = http://theruntime.com/blogs/jacob/archive/2008/01/22/tdd-proven-effective-or-is-it.aspx
|title = TDD Proven Effective! Or is it?
|accessdate = 2008-02-21
|last = Proffitt
|first = Jacob
|quote = So TDD's relationship to quality is problematic at best. Its relationship to productivity is more interesting. I hope there's a follow-up study because the productivity numbers simply don't add up very well to me. There is an undeniable correlation between productivity and the number of tests, but that correlation is actually stronger in the non-TDD group (which had a single outlier compared to roughly half of the TDD group being outside the 95% band).
|archiveurl = https://www.webcitation.org/61G4k4frE?url=http://theruntime.com/blogs/jacob/archive/2008/01/22/tdd-proven-effective-or-is-it.aspx
|archivedate = 2011-08-27
|deadurl = yes
}}
</ref>

Програмісти, які використовують TDD на нових проектах, відзначають, що вони рідше відчувають необхідність використовувати [[зневаджувач]]. Якщо деякі з тестів несподівано перестають проходити, відкат до останньої версії, яка проходить всі тести, може бути продуктивнішим, ніж [[зневадження]].<ref>{{cite web
|url = http://www.gamesfromwithin.com/articles/0502/000073.html
|title = Stepping Through the Looking Glass: Test-Driven Game Development (Part 1)
|accessdate = 2007-11-01
|last = Llopis
|first = Noel
|date = 2005-02-20
|publisher = Games from Within
|quote = Comparing [TDD] to the non-test-driven development approach, you're replacing all the mental checking and debugger stepping with code that verifies that your program does exactly what you intended it to do.
|deadurl = yes
|archiveurl = https://web.archive.org/web/20050222120848/http://www.gamesfromwithin.com/articles/0502/000073.html
|archivedate = 2005-02-22
}}</ref>

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

Незважаючи на те, що при розробці через тестування потрібно написати більшу кількість коду, загальний час, витрачений на розробку, зазвичай виявляється менше. Тести захищають від помилок. Тому час, що витрачається на зневадження, зменшується в рази. <ref>{{cite web
|url = http://www.ipd.uka.de/mitarbeiter/muellerm/publications/edser03.pdf
|title = About the Return on Investment of Test-Driven Development
|accessdate = 2007-11-01
|author =
|last = Müller
|first = Matthias M.
|coauthors = Padberg, Frank
|format = PDF
|publisher = Universität Karlsruhe, Germany
|pages = 6
|archiveurl = https://web.archive.org/web/20070611144008/http://www.ipd.uka.de/mitarbeiter/muellerm/publications/edser03.pdf
|archivedate = 2007-06-11
|deadurl = yes
}}</ref> Велика кількість тестів допомагає зменшити кількість помилок в коді. Усунення дефектів на більш ранньому етапі розробки перешкоджає появі хронічних помилок, що призводять до тривалого та виснажливого ​​зневадження в майбутньому.

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

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

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

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

== Слабкі місця ==
* Головним недоліком TDD є те, що до нього тяжко звикнути.
* Існують завдання, які неможливо (принаймні, на поточний момент) вирішити тільки за допомогою тестів. Зокрема, TDD не дозволяє механічно продемонструвати адекватність розробленого коду в області безпеки даних і взаємодії між процесами. Безумовно, безпека заснована на коді, в якому не повинно бути дефектів, проте вона заснована також на участі людини у процедурах захисту даних. Тонкі проблеми, що виникають у сфері взаємодії між процесами, неможливо з упевненістю відтворити, просто запустивши деякий код.
* Розробку через тестування складно застосовувати в тих випадках, коли для тестування необхідно проходження функціональних тестів. Прикладами може бути: розробка інтерфейсів користувача, програм, що працюють з базами даних, а також того, що залежить від специфічної конфігурації мережі. Керована тестами розробка не передбачає великого обсягу роботи з тестування такого роду речей. Вона зосереджується на тестуванні окремо взятих модулів, використовуючи mock-об'єкти для представлення зовнішнього світу.
* Потрібно більше часу на розробку і підтримку, а схвалення з боку керівництва дуже важливо. Якщо в організації немає впевненості в тому, що керована тестами розробка поліпшить якість продукту, то час, витрачений на написання тестів, може розглядатися як витрачений даремно. <ref>{{cite web
|url = http://people.apache.org/~stevel/slides/testing.pdf
|title = Testing
|last = Loughran
|first = Steve
|date = November 6th, 2006
|publisher = HP Laboratories
|accessdate = 2009-08-12
|format = PDF
|archiveurl = https://web.archive.org/web/20090220052052/http://people.apache.org/~stevel/slides/testing.pdf
|archivedate = 2009-02-20
|deadurl = yes
}}</ref>
* Модульні тести, створювані при розробці через тестування, звичайно пишуться тими ж, хто пише тестований код. Якщо розробник неправильно витлумачив вимоги до застосунку, і тест, і тестований модуль будуть містити помилку.
* Велика кількість використовуваних тестів можуть створити помилкове відчуття надійності, що призводить до меншої кількості дій з контролю якості.
* Тести самі по собі є джерелом накладних витрат. Погано написані тести, наприклад, містять жорстко вшиті рядки з повідомленнями про помилки або схильні до помилок. Щоб спростити підтримку тестів слід повторно використовувати повідомлення про помилки з тестованого коду.
* Рівень покриття тестами, що отримується в результаті розробки через тестування, не може бути легко отриманий згодом. Вихідні тести стають все більш цінними з плином часу. Якщо невдалі архітектура, дизайн або стратегія тестування призводять до великої кількості не пройдених тестів, важливо їх всі виправити в індивідуальному порядку. Просте видалення, відключення або поспішна зміна їх може призвести до прогалин у покритті тестами.


== Джерела інформації ==
== Джерела інформації ==

Поточна версія на 12:45, 6 серпня 2024

Керована тестами розробка (КТР), Розробка через тестування (англ. Test-driven development (TDD)) — технологія розробки програмного забезпечення, яка використовує короткі ітерації розробки, що починаються з попереднього написання тестів, які визначають необхідні покращення або нові функції. Кожна ітерація має на меті розробити код, який пройде ці тести. Нарешті, програміст або група вдосконалюють код для погодження змін. Один із ключових моментів TDD полягає у тому, що підготовка тестів перед написанням самого коду пришвидшує процес внесення змін. Варто зауважити, що керована тестами розробка є методологією розробки програмного забезпечення, а не його тестування.

Test-Driven Development відноситься до концепції екстремального програмування, яка стверджує, що спершу потрібно писати тести, а вже потім код, яка веде свій початок з 1999 року,[1] однак, останнім часом спостерігається загальніший інтерес до даної методології.[2]

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


Вимоги

[ред. | ред. код]

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

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

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

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

Три закони TDD

[ред. | ред. код]
  1. Не можна писати жодного вихідного коду, доки спершу не написано падаючого юніт-тесту (англ. unit test).
  2. Не можна писати більше юніт-тесту ніж необхідно для падіння (непроходження тесту). Помилка компіляції - це також падіння (англ. failing).
  3. Не можна писати більше вихідного коду, ніж необхідно для проходження вдалого юніт-тесту.

Цикл розробки за методологією TDD

[ред. | ред. код]
Графічна репрезентація циклу розробки TDD

Усе нижчевказане базується на книзі Test-Driven Development by Example,[4] котру багато хто вважає канонічним текстом про дану концепцію у її сучасному вигляді.

1. Додати тест

[ред. | ред. код]

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

2. Запустити усі тести, і подивитись, чи вони пройшли

[ред. | ред. код]

Це підтверджує, що усі тести працюють коректно, і, що нові тести помилково не проходять, не вимагаючи при цьому ніякого нового коду.

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

3. Написати код

[ред. | ред. код]

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

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

4. Запустити автоматичні тести, і подивитись, чи пройшли вони успішно

[ред. | ред. код]

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

5. Удосконалити код

[ред. | ред. код]

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

Стиль розробки

[ред. | ред. код]

Керована тестами розробка тісно пов'язана з такими принципами як «роби простіше, дурник» (англ. keep it simple, stupid, KISS) і «вам це не знадобиться» (англ. you ain't gonna need it, YAGNI). Дизайн може бути чистіше і ясніше при написанні лише того коду, який необхідний для проходження тесту. [4] Кент Бек також пропонує принцип «підроби, поки не зробиш» (англ. fake it till you make it).

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

Ідея перевіряти, що тільки написаний тест не проходить, допомагає переконатися, що тест реально щось перевіряє. Тільки після цієї перевірки слід приступати до реалізації нової функціональності. Цей прийом, відомий як «червоний / зелений / рефакторинг», називають «мантрою розробки через тестування». Під червоним тут розуміють тести, які не пройшли, а під зеленим - які пройшли тестування.

Відпрацьовані практики розробки через тестування призвели до створення техніки «Керована приймальними тестами розробка» (англ. Acceptance Test-driven development, ATDD), в якому критерії описані замовником автоматизуються в приймальні тести, що використовуються потім в звичайному процесі керованої модульними тестами розробки (англ. unit test-driven development, UTDD). [5] Цей процес дозволяє гарантувати, що застосунок задовольняє сформульованим вимогам. При керованій приймальними тестами розробці, команда розробників сконцентрована на чіткій задачі: задовольнити приймальні тести, які відображають відповідні вимоги користувача.

Приймальні (функціональні) тести (англ. customer tests, acceptance tests) - тести, що перевіряють функціональність програми на відповідність вимогам замовника. Приймальні тести проходять на стороні замовника. Це допомагає йому бути впевненим у тому, що він отримає всю необхідну функціональність.

Видимість коду

[ред. | ред. код]

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

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

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

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

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


Fake-, mock-об'єкти та інтеграційні тести

[ред. | ред. код]

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

Коли код що розробляється використовує бази даних, вебсервіси або інші зовнішні процеси, має сенс виділити частину тестування, що покривається. Це робиться в два кроки:

  1. Скрізь, де вимагається доступ до зовнішніх ресурсів, повинен бути оголошений інтерфейс, через який цей доступ буде здійснюватися.
  2. Інтерфейс повинен мати дві реалізації. Перша надає доступ до ресурсу, і друга, що є 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 or Rhino Mocks, призначені спростити процес створення mock-об'єктів.

Переваги

[ред. | ред. код]

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

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

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

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

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

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

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

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

Слабкі місця

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

Джерела інформації

[ред. | ред. код]
  1. "Extreme Programming", Computerworld (online), December 2001, webpage: Computerworld-appdev-92 [Архівовано 2011-06-05 у Wayback Machine.].
  2. Newkirk, JW and Vorontsov, AA. Test-Driven Development in Microsoft .NET, Microsoft Press, 2004.
  3. Feathers, M. Working Effectively with Legacy Code, Prentice Hall, 2004
  4. а б Beck, K. Test-Driven Development by Example, Addison Wesley, 2003
  5. Koskela, L. «Test Driven: TDD and Acceptance TDD for Java Developers», Manning Publications, 2007
  6. Burton, Ross (11/12/2003). Subverting Java Access Protection for Unit Testing. O'Reilly Media, Inc. Архів оригіналу за 27 липня 2009. Процитовано 12 серпня 2009.
  7. Newkirk, James (7 червня 2004). Testing Private Methods/Member Variables - Should you or shouldn't you. Microsoft Corporation. Архів оригіналу за 30 червня 2009. Процитовано 12 серпня 2009.
  8. Stall, Tim (1 березня 2005). How to Test Private and Protected methods in .NET. CodeProject. Архів оригіналу за 3 вересня 2009. Процитовано 12 серпня 2009.
  9. Erdogmus, Hakan; Morisio, Torchiano. On the Effectiveness of Test-first Approach to Programming. Proceedings of the IEEE Transactions on Software Engineering, 31(1). January 2005. (NRC 47445). Архів оригіналу за 27 серпня 2011. Процитовано 14 січня 2008. We found that test-first students on average wrote more tests and, in turn, students who wrote more tests tended to be more productive.
  10. Proffitt, Jacob. TDD Proven Effective! Or is it?. Архів оригіналу за 27 серпня 2011. Процитовано 21 лютого 2008. So TDD's relationship to quality is problematic at best. Its relationship to productivity is more interesting. I hope there's a follow-up study because the productivity numbers simply don't add up very well to me. There is an undeniable correlation between productivity and the number of tests, but that correlation is actually stronger in the non-TDD group (which had a single outlier compared to roughly half of the TDD group being outside the 95% band).
  11. Llopis, Noel (20 лютого 2005). Stepping Through the Looking Glass: Test-Driven Game Development (Part 1). Games from Within. Архів оригіналу за 22 лютого 2005. Процитовано 1 листопада 2007. Comparing [TDD] to the non-test-driven development approach, you're replacing all the mental checking and debugger stepping with code that verifies that your program does exactly what you intended it to do.
  12. Müller, Matthias M.; Padberg, Frank. About the Return on Investment of Test-Driven Development (PDF). Universität Karlsruhe, Germany. с. 6. Архів оригіналу (PDF) за 11 червня 2007. Процитовано 1 листопада 2007.
  13. Loughran, Steve (November 6th, 2006). Testing (PDF). HP Laboratories. Архів оригіналу (PDF) за 20 лютого 2009. Процитовано 12 серпня 2009.