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

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

Не е задължително да е така. Разработването на софтуерни приложения трябва да бъде приятно пътуване, пълно с възпитателни изживявания, а не разходка в сградата на училището Silent Hill... след звука на сирената. Направете услуга на себе си и на екипа си и закопчайте кода си!!

Сега бихте попитали „Как да закопча това?“ Е, първо тествайте.

Разработка, управлявана от тестове, и тестване на единици

Нямам намерение да давам пълно обяснение на TDD и Unit Testing, тъй като има много статии и книги по тези теми, които със сигурност могат да ги обяснят по-добре, отколкото аз бих могъл.

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

Опитайте се да мислите със софтуер, както бихте направили за всеки жив организъм. Той расте, променя се, разпространява се и умира, когато не се поддържа. За него важат същите правила на еволюцията:

„Не е най-производителният, нито най-популярният, който ще оцелее, а този, който най-добре реагира на промяната.“

Лента за промени

Тестването на единици не трябва да е задача, която отнема целия ви ден, за да разберете как да тествате дадена функция или да настроите изискан инструмент за тестване (говоря на вас Jasmine+Karma+PhantomJS). Вместо това трябва да бъде маркер на картата и компас, който да ви напътства да създадете следващото революционно приложение. Unit Test трябва да ви каже къде се намирате и къде искате да отидете.

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

Опростеността е нещо, което трябва да бъде част от вашето мислене, когато пишете тестове и всякаква част от кода по този въпрос. И просто,е дума, която описва доста добре „лента“.

Лентата е библиотека за тестване на модули, която предоставя незабавно тестов инструмент, библиотека за твърдения и форматирани резултати с помощта на стандартния TAP (Test Anything Protocol). Това е всичко, без describe(), без притеснение за куките beforeEach или afterEach, без глобални елементи, които замърсяват обхвата, без браузър без глава, нищо друго, освен задаването на очаквания и сравняването с действителните резултати. Мъртво просто.

Кодезът…

Стига философия и нека бързо напред, където е действието. Нека създадем приложение Doge Clicker.

Първоначалните изисквания ще бъдат:

  • Имате изображение на Doge, което при щракване дава цитати: „Така че изображението, много тестово, много щраквания #, уау“, където # е броят щраквания към Doge.

И така тестът започва... о, но първо инсталирайте всички инструменти, от които ще се нуждаем:

~$ npm install -D tape faucet babel webpack babel-loader

Трябва да завършите с конфигурация package.json като тази:

Цикълът TDD върви по следния начин: Тест › Неуспешен › Успешен › Рефакторинг. Така…

Първи тест

Неуспех

~$npm test
> babel-node ./test/index.js | faucet
module.js:338
 throw err;
 ^
Error: Cannot find module ‘../doge-clicker’
 at Function.Module._resolveFilename (module.js:336:15)
 at Function.Module._load (module.js:278:25)
 at Module.require (module.js:365:17)
 at require (module.js:384:17)
 at Object.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:1:24)
 at Module._compile (module.js:460:26)
 at normalLoader (/home/maredith/projects/taping/node_modules/babel/node_modules/babel-core/lib/api/register/node.js:199:5)
 at Object.require.extensions.(anonymous function) [as .js] (/home/maredith/projects/taping/node_modules/babel/node_modules/babel-core/lib/api/register/node.js:216:7)
 at Module.load (module.js:355:32)
 at Function.Module._load (module.js:310:12)
not ok 1 no plan found

Тестът се провали и така трябва да бъде. Както споменах преди, единичните тестове са компас, който ни позволява да изберем правилната посока, като кодираме очакванията предварително. Ако прочетете коментарите в тестовия код, някои дизайнерски решения вече са взети, преди дори да напишете функционален код. Ето защо TDD е известен също като Test Driven Design.

Пас

Сега нека напишем минималното количество код, за да премине тестът:

//doge-clicker.js
export default function doge(){
    return {
         count: 0,
         click (){
             this.count = 1;    
         }    
    }
}

Изпълнете теста си отново...

~$ npm test
> babel-node ./test/index.js | faucet
✓ The Click should increase Doge counter by 1
# tests 1
# pass  1
✓ ok

Ей, зелен тест! досега не е необходим рефактор. Нека тестваме за множество кликвания:

test('3 clicks should increase the counter to 3', expect=>{
   const wow = doge();
    wow.click();
    wow.click();
    wow.click();

    expect.equals(wow.count, 3, '1,2,3 wow');
    expect.end();
});

Изпълнете тест:

~$ npm test
> babel-node ./test/index.js | faucet
✓ The Click should increase Doge counter by 1
⨯ 3 clicks should increase the counter to 3
  not ok 2 1,2,3 wow
    ---
      operator: equal
      expected: 3
      actual:   1
      at: Test.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:19:12)
    ...
  
# tests 2
# pass  1
⨯ fail  1

Провал! хубаво!! сега кодираме, за да премине. Това е корекция на 1 символ:

~$ npm test
> babel-node ./test/index.js | faucet
✓ The Click should increase Doge counter by 1
✓ 3 clicks should increase the counter by 3
# tests 2
# pass 2
✓ ok

И точно когато си мислим, че сме готови, шефът идва и казва:

Шеф – „Помните ли изискването за кликър Doge? добре, сега имаме нужда при всяко кликване броячът да се увеличава с определена сума. Бихте ли променили тази вечер? Имахме нужда от него за вчера.”

Вие — „Но беше поискано днес…“

Шеф – „Точно…“

Е, нека продължим. За щастие, тестовете са силни в този.

test('click(2), click(3), click(5) should increase the count to 10', expect =>{
   const wow = doge();
    wow.click(2);
    wow.click(3);
    wow.click(5);
    expect.equal(wow.count, 10, 'Much click, so number, wow');
    expect.end();
});

Изпълнете тест и се провалите, знаете тренировката. Сега код:

export default function doge (){
    return {
        count: 0,
        click (times){
            this.count += times;
        }
    };
}

Пуснете тест и уауууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууу

~$ npm test
> babel-node ./test/index.js | faucet
⨯ The Click should increase Doge counter by 1
 not ok 1 Wow I can count to 1
 — -
 operator: equal
 expected: 1
 actual: NaN
 at: Test.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:9:12)
 …
⨯ 3 clicks should increase the counter by 3
 not ok 2 1,2,3 wow
 — -
 operator: equal
 expected: 3
 actual: NaN
 at: Test.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:19:12)
 …
✓ click(2), click(3), click(5) should increase the count to 10
# tests 3
# pass 1
⨯ fail 2

Рефактор

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

export default function doge (){
    return {
        count: 0,
        click (times){
            this.count += times || 1;
        }
    };
}

Ако проведем нашите тестове отново...

✓ The Click should increase Doge counter by 1
✓ 3 clicks should increase the counter by 3
✓ click(2), click(3), click(5) should increase the count to 10
# tests 3
# pass 3
✓ ok

Сега сме отново на пистата.

Ако искате да видите пълния проект, клонирайте това git repo и следвайте изходния код. Добавих още тестови проби с коментари, за да можете да разберете какво си мислех в момента на кодиране.

„Колко тест е достатъчен?“

Това е много често задаван въпрос и отговорът ми винаги е:

Никога не си готов. Тръгвате, когато сте достатъчно готови - полковник Граф

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

За тази цел са създадени инструменти за покриване на кодове. BlanketJS, Истанбул, Испарта (Истанбул за ES6), JSCoverage, Coveralls.io са някои от онези страхотни модули, които могат да бъдат интегрирани във вашия работен процес на разработка и CI процес, за да ви дадат тази увереност при вашите тестове. Винаги трябва да се стремите към 100% покритие.

Като пример, можете да забележите в проекта package.json script.cover, който използва Isparta за докладване на анализ на покритието на нашата кодова база.

Последният ми съвет може да са простите правила, които следвам, когато кодирам:

  1. Не повече от 40 SLOC на файл. Защо? Улеснява четенето и ви помага да се съсредоточите върху функцията за просто четене.
  2. Ако дадена функция е трудна за тестване, значи е твърде сложна. Време е да го разбиете до по-прости функционални модули.
  3. Тествайте първо щастливия път, едва след това трябва да погледнете, за да тествате отговорите на възможни сценарии за грешка.
  4. Съхранявайте макетите и мъничетата до минимум (база данни, файлова система, уеб API). Ако вашият модул има твърде много зависимости, това е миризма, показваща, че той прави твърде много. Върнете се към предложение 2.

The Take Away

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

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

Embrace Test Първа разработка. Изисква дисциплина и усилия, но наистина си заслужава. След като го разберете, няма да почувствате същия код за писане без тестове, които да ви подкрепят.

Благодаря, че прочетохте толкова дълго. Чувствайте се свободни да добавяте коментари и препоръчвате, ако смятате, че това писание е полезно или поне забавно.

Присъединете се към мен в следващата статия, където ще разгледам Test First Development с помощта на React, Flux и Tape, за да създам забавно приложение.