Като програмисти ние постоянно пишем код. С времето този код „гние“. Гниенето е процесът, в който кодът вече не е актуален, не отговаря на промените в системата. Всеки път, когато се опитаме да променим този код, той се „чупи“. А този който го е променил, става обречен на това да го поддържа от тук натам. Причината да се чупи кодът е в това, че той не се поддава на промяна. Всъщност никой не ни спира да направим тази промяна, просто ни е страх, че ще сме длъжни да го поддържаме. Това е главният проблем, който решава Test Driven Development. Проблемът със страха от промяна.

Представете си, че имате бутон, който като го натиснете светва лампичка. Ако лампичката светва зелено означава, че вашият код работи правилно. Ако светне червено означава че, нещата, които сте направили току-що не работят правилно и трябва да бъдат поправени. След като ги оправите, отново натискате бутона и проверявате дали всичко е наред. Всички бихме искали такъв бутон, нали? Този бутон се нарича Test Driven Development (TDD).

Има три основни правила при прилагане на процеса TDD. На пръв поглед те биха могли да ви шокират. Ще поставят под въпрос всичко, което сте знаели за програмирането до момента. А накрая просто ще ви откажат от целия процес. Трите правила са:

–       Нямате право да напишете една линия продукционен код, без преди това да сте написали неуспешно преминаващ тест.
–       Нямате право да напишете повече от теста, от колкото е достатъчно той да не премине успешно (невъзможността за компилация също е достатъчна).
–       Нямате право да напишете повече продукционен код, от този който е достатъчен да накара текущо провалящият се тест, да премине.

Изпълнявайки тези правила ще се окажете в един цикъл, дълъг около 30 секунди. И това ще важи за всяка линия продукционен код, който пишете. Това звучи безумно нали? Ако трябва да напишем система, която е 100 000 реда код, ще ни отнеме повече от 800 човекочаса само за написването на кода. Ако включим и всички останали процеси необходими за изграждането на един проект, времето става зашеметяващо. Аз лично съм участвал в екип изпълняващ такъв проект, но ние го изградихме за нула време. Особено когато по-голяма част от кода беше дублиран на десетки места. Тогава какъв е смисълът умишлено да забавяме процеса на разработване с някакъв такъв процес като TDD? Смисълът идва в момента когато трябва да се добави нова функционалност или да се отстрани бъг. Времето за нещо, което трябва да отнеме минути – става часове, а дори и дни. В този момент над 800 човекочаса са загубени само в дебъгване. Да не говорим работата в екипа става много трудна. Стрес и отчаяние са единствените неща, които изпитват хората участващи в такъв проект. Съвсем различна е картинката в екип, който прилага TDD. Когото и да посочите, когато и да го посочите всичко, което е правил преди минути е било напълно работещо. Представете си колко е лесно да се работи в този екип. Колко време според вас се прекарва в дебъгване? Отговорът варира от два до десет пъти по-малко. Всеки в този екип не се притеснява дали е написал най-доброто решение или дали е избрал най-правилната архитектура, защото по всяко едно време, може да „изчисти“ кода си и да провери дали той работи.


 

Събитие на фокус:

Testing NodeJS Code

 

 


Всичко това звучи невероятно, но в момента, в който се научите да прилагате правилно TDD, започвате да виждате всички тези позитиви на практика.

Нека Ви дам пример как изглежда този процес на практика. Имаме за задача, да напишем функция, която трябва да форматира текст. Тя трябва да приема като параметър текст за форматиране и максималната дължина на реда към който искаме да форматираме.

Това трябва да стане като се намери най-подходящия интервал, който да бъде заменен със символ за нов ред. Ако се окаже, че мястото за форматиране попада в някоя дума, то тази дума трябва да бъде пренесена на нов ред.

За демонстрацията ще използвам езика C# и тестова рамка NUnit. С цел примерът да бъде максимално кратък и ясен, ще се наложи да се съкратят някои от възможните сценарии за тестване, както и да се направят някои компромиси с организирането на кода, като например поставянето на всички тестови случаи в един тест, използването на не най-оптимални алгоритми за решаване на проблем и др.

Да започнем с конструирането на празен тест, за да се уверим, че средата за разработване е подготвена правилно.

Следвайки първото правило на TDD, започваме с писането на първия тест за нашата функция (поставена в примерен клас TextWrapper).

В този момент трябва да спрем, защото трябва да приложим второто правило на TDD. Тестът ни не се компилира и за да продължим трябва първо да създадем класа TextWrapper.

Оставяме го празен за сега, тъй като третото правило ни задължава да напишем само толкова продукционен код, колкото е необходимо за преминаването на теста.

Следващата стъпка, е да напишем първия реален тест като извикаме функцията за форматиране на текст. Но от какъв тип да е този тест? Най-удачно е да започнем с тестването на най-нестандартните начини, по които може да се извика функцията. Например подаване на нулева стойност за текст.

В този момент отново трябва да приложим второто правило на TDD. За да продължим, първо трябва да създадем функцията Wrap. Със създаването й трябва да изберем какво да върнем като резултат от извикването й. Тук си задаваме въпроса какво е най-лесното нещо, което може да се направи, за да премине тестът. И това нещо е просто да върнем празна стойност.

След като го направим, можем да пуснем тестовете и да се уверим, че преминават успешно. Сега отново можем да продължим с писането на следващия тест.

Друг нестандартен начин за извикване на функцията е подавайки празна стойност.

Ако пуснем тестовете в момента, ще видим че те преминават успешно. Това значи че, трябва да продължим с писането на тестове. Следващия тест може да бъде направен с най-простия валиден текст, този състоящ се само от един символ.

Пускаме тестовете и виждаме, че не преминават успешно. Връщаме се в тялото на функцията където трябва да направим промяна, за да успеем да преминем проваления тест. Отново се питаме какво е най-лесното нещо, което може да се направи. То е да направим проверка за текста, който е подаден. Предлагам примерно решение:

Отново пускаме тестовете и виждаме, че те преминават успешно. Тук вече може да се усети този цикъл дълъг 30 секунди. Пишем тест, който не преминава и после пишем код, който да го накара да премине. Правим постепенни промени в тестовете и постепенни промени в кода. По този начин плавно и умерено вървим към решаването на задачата. Разбира се, във всеки един момент можем да направим подобрения по кода, без да се страхуваме, че ще развалим коректната работа на програмата. Продължаваме със следващия тест, този с два символа, единият от които трябва да бъде пренесен на нов ред. Той може да изглежда ето така:

За да го накараме да премине успешно трябва да добавим още няколко проверки и да напишем опростен алгоритъм за добавяне на нов ред. Примерното решение изглежда ето така:

Тестовете преминават успешно и можем да продължим със следващия тест. Нека пробваме случая, при който имаме три символа, всеки от които трябва да е на отделен ред.

За да решим проблема с този тест можем да извикваме нашата функция рекурсивно в самата нея.

Всички тестове преминават успешно. Сега можем да преминем към тест за най-простата ситуация, в която имаме интервал в текста – два символа разделени с интервал.

За да преминем този тест, отрязваме всички интервали преди и след текста при рекурсивното извикване.

Последният случай, който трябва да тестваме, е дължината на форматирането да попадне в средата на някоя дума. Правим го по възможно най-опростения начин:

Тук, вече ще се наложи да добавим проверка за мястото на последния интервал във всяка част от текста и използването му като място, където ще се постави новият ред в текста.

След нанасянето и на тези промени можем да приемем, че сме готови с изпълнението на задачата. Истинския краен тест можем да направим с част от текст на песента на Гери-Никол и 100 Кила:

„Напра’о ги убивам! Пръскам! Чашите високо горе Да се чува дзън дзън Партито е high с тебе яко до край Искам с тебе ми е най на-на-на най“.

Ако го форматираме до 20 символа на ред трябва да се получи това:

“Напра’о ги убивам!
Пръскам! Чашите
високо горе Да се
чува дзън дзън
Партито е high с
тебе яко до край
Искам с тебе ми е
най на-на-на най”

Нека го добавим като последен случай в нашите тестове:

По този начин завършихме задачата успешно. Качеството на кода на функцията, вече може да се подобрява без да има опасност нещо да се развали. Във всеки един момент, можем да изпълним тестовете и да проверим дали всичко работи както се очаква.

Прилагането на Test Driven Development е признак на професионализъм. Всеки, трябва да познава този процес и да се стреми да го прилага в работата си. Той е един от многото инструменти, с които се постига високо качество на кода. Той е единственият начин да се подсигурим, че тестовете, които пишем са максимално пълни и покриват най-голям брой реални ситуации.

Допълнителни материали:
http://a.co/0h3kZsC
https://cleancoders.com/videos



За автора: 
Костадин Капсъзов

 

– Костадин Капсъзов се занимава професионално с програмиране от 2012 година.
– Работил е за над 10 различни български и международни компании, като трупа опит и знания в различни сфери на бизнеса.
– Успешно участва в над 13 проекта, използващи различни програмни езици като: C#, C++, Java, Python, JavaScript, Objective-C и др.
– В момента е програмист в екипа „Нови разработки и Иновации“ на българската технологична компания UltraPlay.
– От 2014 г. Започва активно да се интересува от „Software Craftsmanship“ – подход за разработка на софтуер, който набляга на качеството на програмните уменията на софтуерните разработчици. Около този подход се основава гилдия, която споделя идеите, описани от Анди Хънт, в книгата „The Pragmatic Programmer“ и книгата на Дейв Томас „Software Craftsmanship“. Тези идеи описват разработката на софтуер като занаят, подобен на занаятите практикувани от занаятчийските гилдии през Средновековековието.


 

Стани част от потребителските групи на DEV.BG. Виж всички потребителски групи и избери най-интересните за теб.

Визия: Личен архив

Прочети още:
6 от най-популярните Machine Learning алгоритми – приложения и възможности
Какво означава една система да е „reactive“? Основна концепция на Reactive programming

 

 

Share This