Как да станем по-добри при тестване с разработена чрез тест разработка

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

Всички сме се запознали в даден момент с програмист, който е казал нещо като „тестовете са безполезни“, „Това отнема твърде много усилия“ или „Не се съмнявам в кода си. Защо да си губя времето за тестове? “. Не ги слушайте Тестът е от съществено значение.

Една чудесна причина е, че тестовете правят кода ви по-стабилен и намаляват шансовете ви за получаване на грешки. Може да си мислите, че това не е вярно, защото знаете всяко малко от вашия код. Искам да кажа, че сте го изградили, така че защо да пишете тестове за неща, които вече знаете?

Е, нека приемем, че изграждате приложение за времето. Кодирате няколко дни или няколко седмици, така че овладявате кода си.

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

Тогава ще си помислите „Каквото и да е, не знаех как да кодирам. Просто ще оставя това приложение непроменено и ще премина към нещо друго. "

Да вземем друг пример. След месеци упорит труд най-накрая кацнахте тази работа на програмист, която винаги сте искали! Интегрирате се в екип и започвате да изграждате нещо. Работите върху кода на другите и обратно. И нещата ще се счупят. Ако екипът не е интегрирал тестове в приложението си, желая ви късмет да го отстраните.

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

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

Това е моята цел днес: да подобря уменията си за тестване. Ще открием тестване на единици и тестово разработена разработка с Jest (инструмент за тестване на JavaScript) чрез създаване на стек и тестване.

Разбира се, има и други инструменти за тестване, които можете да използвате като Mocha и Chai. Но можем да използваме Jest веднага от кутията. Бързо е и всичко е вградено: библиотека с твърдения, макети, тестове за моментни снимки. Така че нека започнем!

Тестване на единица

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

С две думи, тестът на единицата се състои в тестване на малки части от вашия код: функции, методи на класове и т.н. Вие им давате вход и потвърждавате, че получавате очакваната продукция.

Ето предимствата на тестване на единица:

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

Тестово управлявана разработка (TDD)

За да разберете и използвате тестово разработената разработка, просто приложете тези две правила:

  • Напишете тест, който се проваля, преди дори да напишете кода.
  • След това напишете не повече код, отколкото е достатъчно за преминаване на неуспешния тест.

Когато използваме TDD, говорим и за цикъла Red / Green / Refactor.

  • Червено: пишете неуспешен тест без да пишете кода.
  • Зелено: напишете най-простия код, за да направите теста. Дори ако кодът ви изглежда тъп или прост.
  • Refactor: Refactor кодът, който сте написали, ако е необходимо. Наистина се уверихте, че това, което сте тествали, има правилното поведение. Няма нужда да се притеснявате, ако рефакторирате кода, тестовете на вашата единица ще се счупят, ако нещо се обърка.

Звучи теоретично? Не се притеснявайте Ще разберете, като практикувате.

Структуриране на тестов файл

Jest предоставя функции за структуриране на вашите тестове:

  • опис: използва се за групиране на вашите тестове и описание на поведението на вашата функция / модул / клас. Отнема два параметъра. Първият е низ, описващ вашата група. Вторият е функция за обратно извикване, при която имате своите тестови случаи или функции на куката.
  • това или тест: вашият тестов случай, тоест вашия тест за единица. Параметрите са абсолютно същите като описаните. Трябва да е описателен. Името на вашия тест зависи от вас, но е доста конвенционално да започнете с „Трябва“.
  • beforeAll (afterAll): функция на куката, която се изпълнява преди (и след) всички тестове. Необходим е един параметър, който е функцията, която ще стартирате преди (и след) всички тестове.
  • beforeEach (afterEach): функция на куката, която се изпълнява преди (и след) всеки тест. Той отнема един параметър, който е функцията, която ще стартирате преди (и след) всеки тест.

Трябва да знаете и следното, преди да напишете някакъв тест:

  • Можете да пропуснете вашия тест, като използвате .skip on description и го: it.skip (...) или description.skip (...). Използвайки .skip, казвате на Jest да игнорира теста или групата.
  • Можете да изберете точно кои тестове искате да стартирате, като използвате .only on description и то: it.only (...) или description.only (...). Полезно е, ако имате много тестове и искате да се съсредоточите само върху един тест или ако искате да „отстраните грешки“ в своите тестове.

Настройване на Jest

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

Като предпоставки се нуждаете само от Node.js и npm или Прежда. Уверете се, че използвате последната версия на Node.js, тъй като ние ще използваме ES6. Създайте нова директория и я инициализирайте.

mkdir тест-пример & & cd тест-пример
npm init -y
# ИЛИ
прежда init -y

The -y отговаря да на всички въпроси на npm или прежда. Трябва да е генерирал много основен файл package.json.

След това добавете Jest в зависимостите си от програмисти:

прежда добавяме --dev

Накрая добавете следния скрипт във вашия package.json:

"скриптове": {
  "тест": "е"
}

тест за прежда ще стартира вашите тестови файлове във вашата директория. По подразбиране Jest разпознава файловете, които се намират в директория, наречена __tests__, или файловете, които завършват или .spec.js или .test.js.

И това е всичко. Готови сте да напишете първите си тестове.

кибрит

Когато тествате нещо, имате нужда от вход и очакван резултат. Ето защо Jest предоставя матрици за тестване на нашите стойности:

очакваме (вход) .matcher (изход)

Jest има много съвпадения, така че тук са най-често срещаните:

  • toBe: сравнява строгото равенство (===).
очаквайте (1 + 1) .toBe (2)
нека тестовеAreEssential = вярно
очакваме (testAreEssential) .toBe (вярно)
  • toEqual: сравнява стойностите между две променливи или масиви или обекти.
нека arr = [1, 2]
arr.push (3)
очаквам (arr) .toEqual ([1, 2, 3])
нека x = 1
х ++
очаква (х) .toEqual (2)
  • toBeTruthy (toBeFalsy): казва дали стойността е вярна (false).
очакваме (нула) .toBeFalsy ()
очакваме (неопределени) .toBeFalsy ()
очакваме (фалшиво) .toBeFalsy ()
очаквам ("Здравей свят"). toBeTruthy ()
очаквам ({foo: 'bar'}). toBeTruthy ()
  • не: трябва да бъде поставен пред съответния мач и да връща обратното на резултата на съответния матч.
очакваме (нула) .not.toBeTruthy ()
// същата като очаквайте (нула) .toBeFalsy ()
очаква ([1]). not.toEqual ([2])
  • toContain: проверява дали масивът съдържа елемента в параметъра.
очаквайте (['Apple', 'Banana', 'Strawberry']). toContain ('Apple')
  • toThrow: проверява дали дадена функция хвърля грешка
функция connect () {
  хвърли нов ConnectionError ()
}
очакваме (свързване) .toThrow (ConnectionError)

Това не са единствените мачове. Можете да намерите всички мачове от Jest тук.

Първи тестове

Сега, ние ще напишем първия си тест и ще си играем с нашите функции. Първо, създайте във вашата директория файл, наречен example.spec.js, и поставете следното съдържание:

description ('Example', () => {
  предиAll (() => {
    console.log ("работи преди всички тестове")
  })
  afterAll (() => {
    console.log ("работи след всички тестове")
  })
  предиEach (() => {
    console.log ("работи преди всеки тест")
  })
  afterEach (() => {
    console.log ("работи след всеки тест")
  })
  it ('Трябва да направя нещо', () => {
    console.log ('първи тест')
  })
  it ('Трябва да направя нещо друго', () => {
    console.log ('втори тест')
  })
})

Имайте предвид, че не е необходимо да импортираме всички функции, които използваме. Те вече са предоставени от Jest.

Изпълнете тест на преждата:

Изследване на различни тестови функции

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

Сега премахнете всички функции на куката и добавете .skip към първия тест:

description ('Example', () => {
  it.skip ('Трябва да направя нещо', () => {
    console.log ('първи тест')
  })
  it ('Трябва да направя нещо друго', () => {
    console.log ('втори тест')
  })
})

Изпълнете теста на преждата отново:

Логично е откакто сте пропуснали тест. Първият просто няма да стартира.

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

description ('Example', () => {
  it ('Трябва да направя нещо', () => {
    console.log ('първи тест')
  })
  it ('Трябва да направя нещо друго', () => {
    console.log ('втори тест')
  })
  it.only ('Трябва ли да го направя', () => {
    console.log ('трети тест')
  })
})

Изпълнете теста на преждата още веднъж:

Отново логично. Казваш на Jest да пусне само третия си тест. Така че виждате само третия тест в конзолата.

Тестване на стек с тестово разработена разработка

Няма повече теория. Време за тренировка.

По-нататък ще направим проста реализация на стек в JavaScript с тестова разработка.

Като напомняне стекът е структура от данни, по-точно LIFO структура: Last In, First Out. Има три основни операции за изпълнение на стека:

  • push: бута елемент в горната част на стека.
  • pop: премахва елемента в горната част на стека.
  • peek: връща последния елемент в горната част на стека.

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

  • items: елементите на стека. Ще използваме масив, за да реализираме стека.
  • капацитет: капацитетът на стека.
  • isEmpty (): връща true, ако стекът е празен, false в противен случай.
  • isFull (): връща true, ако стекът е достигнал максималния си капацитет, тоест, когато не можете да натиснете друг елемент. Връща невярно в противен случай.
  • push (елемент): натиска елемент върху стека. Връща пълно, ако стекът е пълен, елементът е натиснат по друг начин.
  • pop (): премахва последния елемент от стека. Връща празно, ако стека е празен, елементът се извежда по друг начин.
  • peek (): връща елемента в горната част на стека (последният натиснат след това). Връща празно, ако стека е празен, връща елемента по друг начин.

Ще създадем два файла stack.js и stack.spec.js. Използвах разширението .spec.js, защото съм свикнал с него, но вие сте свободни да използвате .test.js или да му дадете друго име и да го поставите под __tests__.

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

const Stack = изисквам ('./ стек')

За тези, които се чудят защо не съм използвал импортиране тук, това е, защото последната стабилна версия на Node.js не го поддържа от днес. Можех да добавя Babel, но не искам да претоварвам този урок. Така че нека се придържаме към изискването

Едно добро нещо, което трябва да направите, когато тествате клас или функция, е да започнете своя тест, като опишете кой файл или клас да тествате. Ето, става въпрос за стек:

description ('Stack', () => {
})

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

it ('Трябва да конструира стека с даден капацитет', () => {
  нека стека = нов стек (3)
  очаква (stack.items) .toEqual ([])
  очакваме (stack.capacity) .toBe (3)
})

Обърнете внимание, че ние използваме toEqual, а не toBe за stack.items, тъй като те не се отнасят към един и същ масив. Затова трябва да сравним само техните стойности.

Сега, стартирайте тест на прежда stack.spec.js. Пускаме Jest на определен файл, защото не искаме да бъдем замърсени от другите тестове. Ето резултата:

Stack не е конструктор. Разбира се. Все още не сме създали нашия клас Stack и го предоставихме като конструктор.

В stack.js създайте своя клас, конструктор и експортирайте класа:

клас стек {
  конструктор () {
  }
}
module.exports = Стек

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

Тъй като не сме задали елементи в конструктора, Jest очакваше елементите в масива да се равняват [], но стана неопределено. След това трябва да инициализирате елементите:

конструктор () {
  this.items = []
}

Ако отново стартирате теста, ще получите същия вид грешка за капацитет, така че ще трябва да зададете и капацитета:

конструктор (капацитет) {
  this.items = []
  this.capacity = капацитет
}

Изпълнете нашия тест:

Да! PASS. Видяхте ли как написахме решението? За това е TDD. Той покрива кода ви по всяко време и ви позволява да напредвате бавно към решението, докато коригирате неуспешните тестове. Надявам се сега тестването да има по-голям смисъл! Така че, нека да продължим, нали?

празно е

За да тестваме isEmpty, ще инициализираме празен стек, тестваме дали isEmpty се връща вярно, добавяме елемент и го тестваме отново.

it ('Трябва да има функция isEmpty, която се връща вярно, ако стекът е празен и невярен в противен случай', () => {
  нека стека = нов стек (3)
  очакваме (stack.isEmpty ()). Тоби (вярно)
  stack.items.push (2)
  очакваме (stack.isEmpty ()). Тоби (фалшиво)
})

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

TypeError: stack.isEmpty не е функция

За да разрешим този проблем, ще трябва да създадем isEmpty в класа на Stack:

празно е () {
}

Ако стартирате теста, трябва да получите друга грешка:

Очаквано: вярно
Получено: неопределено

Има смисъл. Нищо не се добавя вътре isEmpty. Стекът е празен, ако в него няма елементи:

празно е () {
  върнете this.items.length === 0
}

е пълен

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

тласък

Тук трябва да тестваме три различни неща:

  • нов елемент трябва да бъде добавен отгоре на стека.
  • push връща „Full“, ако стека е пълен.
  • наскоро натиснатият елемент трябва да бъде върнат

Ще създадем още един блок, използвайки description за push. Вкарайте този блок във вашия основен описателен блок.

description ('Stack.push', () => {
  
})

Добавяне на елемент

За да го тестваме, ще създадем нов стек и ще избутаме елемент. Последният елемент от масива от елементи трябва да бъде елементът, който току-що добавихте.

description ('Stack.push', () => {
  it ('Трябва да добавите нов елемент в горната част на стека', () => {
    нека стека = нов стек (3)
    stack.push (2)
    очаквам (stack.items [stack.items.length - 1]). toBe (2)
  })
})

Ако стартирате теста, виждате, че натискането не е дефинирано, което още веднъж е напълно нормално. push ще се нуждае от параметър, за да натисне нещо върху стека:

push (елемент) {
 this.items.push (елемент)
}

Тестовете преминават отново. Забелязали ли сте нещо? Ние запазваме копирането на този ред:

нека стека = нов стек (3)

Това е доста досадно За щастие за нас, ние имаме метода beforeEach, който ни позволява да направим някаква настройка преди всеки тестов старт. Защо тогава да не конструираме стека в този метод?

нека стека
предиEach (() => {
 стек = нов стек (3)
})

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

Странична бележка: Ако бихме използвали предиAllinstead на beforeEach, ще трябва да предоставим и метод AfterEach. В действителност стекът ще бъде споделен във всички тестове. Това е проблем, тъй като натискаме на стека, поп и т.н. Така че ще трябва да нулираме стека след всеки тест:

afterEach (() => {
 stack.items = []
})

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

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

const Stack = изисквам ('./ стек')
description ('Stack', () => {
  нека стека
  предиEach (() => {
    стек = нов стек (3)
  })
  it ('Трябва да конструира стека с даден капацитет', () => {
    очаква (stack.items) .toEqual ([])
    очакваме (stack.capacity) .toBe (3)
  })
  it ('Трябва да има функция isEmpty, която се връща вярно, ако стекът е празен и невярен в противен случай', () => {
    stack.items.push (2)
    очакваме (stack.isEmpty ()). Тоби (фалшиво)
  })
  description ('Stack.push', () => {
    it ('Трябва да добавите нов елемент в горната част на стека', () => {
      stack.push (2)
      очаквам (stack.items [stack.items.length - 1]). toBe (2)
    })
  })
})

Тестване на върнатата стойност

Ето и теста:

it ('Трябва да върне новия елемент, натиснат в горната част на стека', () => {
  нека elementPushed = stack.push (2)
  очакваме (elementPushed) .toBe (2)
})

Когато стартирате теста, получавате:

Очаквано: 2
Получено: неопределено

Наистина. Нищо не се връща вътре в push! Трябва да поправим това:

push (елемент) {
  this.items.push (елемент)
  връщащ елемент
}

Връща пълно, ако стека е пълен

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

it („Трябва да се върне пълно, ако човек се опита да натисне в горната част на стека, докато е пълен“, () => {
  stack.items = [1, 2, 3]
  let element = stack.push (4)
  очаквам (stack.items [stack.items.length - 1]). toBe (3)
  очакваме (елемент) .toBe ( "Пълен")
})

Трябва да получите тази грешка, когато стартирате теста:

Очаква се: 3
Получени: 4

Така елементът беше изтласкан. Това не е това, което искаме. Първо трябва да проверим дали стека е пълен, преди да добавим нещо:

push (елемент) {
  ако (this.isFull ()) {
    връщане „Пълно“
  }
  
  this.items.push (елемент)
  връщащ елемент
}

Тестовете минават сега. Завършихме с натискане!

Упражнение: поп и пик

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

Някои съвети:

  • Попът наистина е подобен на натискане на тест.
  • Peek наистина е подобен на поп тест-разумно също!
  • Досега не сме реконструирали кода, защото нямаше нужда да го префабрикуваме. В тези функции може да има начин да префабрикувате кода, след като напишете тестовете и да ги накарате да преминат. Без притеснения, ако промените кода, тестовете са тук, за да знаете какво не е наред.

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

Решение

Как беше упражнението? Успяхте ли? Надявам се. Ако не, не се притеснявайте, тестването отнема време и усилия и тестовете в началото са трудни за писане.

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

ако (this.isEmpty ()) {
  върнете „Празна“
}
върнете this.items.pop ()

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

Изпълнете тестовете си последен път:

Не само стекът ви е тестван, но и вие сте документирали кода си много добре. Само като разгледаме резултатите от тестовете, веднага можем да видим как се държи вашият стек.

Писането на тестове дълбоко подобрява качеството на вашия код. Надявам се, че сега разбирате силата на тестването и тестово разработената разработка.

Този урок е част от новия ми курс: Създаване и тестване на приложение от Scratch с Vue.js. Той ви учи на всичко, което трябва да знаете, за да създадете страхотно приложение Vue: Основи на Vue, API, тестване, стайлинг, внедряване и други!

Ако ви е харесал този урок, уведомете ме, като ми дадете няколко хлопки. Винаги е чудесно да знаете, че работата ви се оценява.