Jak se zlepšit při testování s vývojem založeným na testech

Testování je důležitá dovednost, kterou by měl mít každý vývojář. Někteří vývojáři se přesto zdráhají testovat.

Všichni jsme se někdy setkali s vývojářem, který řekl něco jako „testy jsou k ničemu“, „vyžaduje příliš mnoho úsilí“ nebo „Nepochybuji o kódu. Proč ztrácet čas testováním? “. Neposlouchejte je. Testování je zásadní.

Jedním z velkých důvodů je to, že testy zvyšují stabilitu kódu a snižují vaše šance na získání chyb. Možná si myslíte, že to není pravda, protože znáte každý kousek svého kódu. Myslím, že jste to postavili, tak proč byste psal testy na věcech, které už znáte?

Předpokládejme, že stavíte aplikaci pro počasí. Kódovali jste několik dní nebo týdnů, takže svůj kód ovládáte.

Nyní předpokládejme, že přestanete tuto aplikaci stavět a o několik měsíců později se k ní vracíte. Nepamatujete si všechny podrobnosti svého starého kódu. Změníte to a sakra, něco se zlomilo. Jak to vyřešíte? Když se podíváte na každý soubor, který jste vytvořili, a vyladíte je, aby to znovu fungovalo? Může to fungovat. Ale geez, změnou tohoto souboru jsi zlomil něco jiného.

Potom si možná pomyslíte: „Ať už jsem nevěděl, jak kódovat. Tuto aplikaci nechám beze změny a přejdu k něčemu jinému. “

Vezměme si další příklad. Po měsících tvrdé práce jste konečně přistál na této vývojářské práci, kterou jste vždy chtěli! Začleníte se do týmu a začnete něco budovat. Pracujete na kódu druhých a naopak. A věci se zlomí. Pokud tým do své aplikace nezačlenil testy, přeji vám hodně štěstí při ladění.

Každá technická společnost by měla psát testy, když staví software nebo aplikace. Takže nechcete být tím člověkem, který neví, jak v prvních týdnech testovat a zápasit s písemnými testy.

Takže ano, psaní testů vyžaduje čas. Ano, je to zpočátku těžké. Ano, vytváření aplikace zní mnohem zajímavěji. Testy jsou však zásadní a šetří čas, když jsou správně implementovány.

To je dnes můj cíl: zlepšit vaše testovací dovednosti. Pomocí Jest (testovací nástroj JavaScriptu) objevíme testování jednotek a vývoj řízený testováním vytvořením zásobníku a jeho testováním.

Samozřejmě existují i ​​další testovací nástroje, které můžete použít jako Mocha a Chai. Ale můžeme použít Jest hned po vybalení z krabice. Je to rychlé a vše je vestavěné: knihovna prosazování, zesměšňování, testování snímků. Tak pojďme začít!

Testování jednotky

Když se rozhodnete testovat aplikaci, narazíte na různé typy testování: testování jednotek, testy integrace a funkční testy. V tomto tutoriálu se zaměříme na testy jednotek. I když jsou funkční a integrační testy také důležité, je obtížnější je nastavit a implementovat než testy jednotek. Kromě toho již získáte velkou hodnotu z jednotkových testů.

Stručně řečeno, test jednotky sestává z testování malých částí kódu: funkce, metody tříd atd. Zadáte jim vstup a ověříte, že dostanete očekávaný výstup.

Výhody testování jednotky jsou následující:

  • Díky tomu je váš kód stabilnější.
  • Je snazší změnit technické provedení funkce bez změny jejího chování.
  • Dokumentuje váš kód. Uvidíte proč brzy.
  • Nutí vás mít skvělý design kódu. Ve skutečnosti je špatně navržený kód často obtížnější testovat.

Testem řízený vývoj (TDD)

Chcete-li porozumět a používat vývoj založený na testech, použijte tato dvě pravidla:

  • Napište test, který se nezdaří, ještě než zapíšete kód.
  • Poté nezapište více kódu, než je dostačující pro úspěšné provedení testu.

Když použijeme TDD, mluvíme také o cyklu červená / zelená / refaktor.

  • Červená: píšete neúspěšný test bez zápisu kódu.
  • Zelená: napište nejjednodušší kód, aby test proběhl. I když se kód zdá hloupý nebo jednoduchý.
  • Refaktor: Refaktor je kód, který jste v případě potřeby napsali. Opravdu jste se ujistili, že to, co jste testovali, má správné chování. Není třeba si dělat starosti, pokud změníte kód, vaše testy jednotek se rozpadnou, pokud se něco pokazí.

Zní to teoreticky? Nebojte se. Rozumíte praktikování.

Struktura zkušebního souboru

Jest poskytuje funkce pro strukturování vašich testů:

  • description: používá se k seskupení testů a popisu chování vaší funkce / modulu / třídy. Trvá dva parametry. První z nich je řetězec popisující vaši skupinu. Druhým je funkce zpětného volání, ve které máte zkušební případy nebo funkce zavěšení.
  • nebo test: váš testovací případ, tj. test vaší jednotky. Parametry jsou přesně stejné jako v popisu. Musí to být popisné. Pojmenování testu je jen na vás, ale je docela běžné začít s „Měl“.
  • beforeAll (afterAll): funkce zavěšení, která běží před (a po) všech testech. Trvá jeden parametr, což je funkce, kterou spustíte před a po všech testech.
  • beforeEach (afterEach): funkce háčku, která běží před (a po) každém testu. Trvá jeden parametr, což je funkce, kterou spustíte před a po každém testu.

Před napsáním jakéhokoli testu musíte znát také následující:

  • Test můžete přeskočit pomocí .skip on description a it: it.skip (...) nebo description.skip (...). Pomocí .skip říkáte Jestovi, aby test nebo skupinu ignoroval.
  • Můžete přesně vybrat, které testy chcete spustit pomocí .only on description a it: it.only (...) nebo description.only (...). Je užitečné, pokud máte spoustu testů a chcete se zaměřit pouze na jeden test, nebo pokud chcete své testy „ladit“.

Nastavení Jestu

Abychom vám ukázali testovací funkce, které jsme použili výše, musíme nastavit Jest. Nebojte se, bude to mrtvé jednoduché.

Jako předpoklady potřebujete pouze Node.js a npm nebo Yarn. Ujistěte se, že používáte nejnovější verzi souboru Node.js, protože budeme používat ES6. Vytvořte nový adresář a inicializujte jej.

mkdir test-example && cd test-example
npm init -y
# NEBO
příze in -y

-Y odpoví ano na všechny otázky npm nebo příze. Měl vygenerovat velmi základní soubor package.json.

Poté přidejte do svých závislostí vývojáře Jest:

příze přidat žid - dev

Nakonec přidejte do svého balíčku.json následující skript:

"skripty": {
  "test": "jest"
}

test příze spustí testovací soubory ve vašem adresáři. Ve výchozím nastavení Jest rozpoznává soubory, které jsou uvnitř adresáře s názvem __tests__, nebo soubory, které končí buď .spec.js nebo .test.js.

A to je vše. Jste připraveni napsat své první testy.

Zápasníci

Když něco otestujete, potřebujete vstup a očekávaný výstup. To je důvod, proč Jest poskytuje zápasníky k testování našich hodnot:

očekávat (vstup) .matcher (výstup)

Jest má hodně zápasníků, takže zde jsou nejběžnější:

  • toBe: porovnává přísnou rovnost (===).
očekávat (1 + 1) .toBe (2)
let testsAreEssential = true
očekávat (testAreEssential) .toBe (true)
  • toEqual: porovnává hodnoty mezi dvěma proměnnými nebo poli nebo objekty.
let arr = [1, 2]
arr.push (3)
očekávat (arr) .toEqual ([1, 2, 3])
nechť x = 1
x ++
Očekávejte (x) .Equal (2)
  • toBeTruthy (toBeFalsy): řekne, zda je hodnota true (false).
očekávat (null) .toBeFalsy ()
očekávat (nedefinováno) .toBeFalsy ()
očekávat (false) .toBeFalsy ()
očekávat ("Ahoj svět"). toBeTruthy ()
očekávat ({foo: 'bar'}). toBeTruthy ()
  • ne: musí být umístěn před zápasníkem a vrací opak výsledku zápasu.
očekávat (null) .not.toBeTruthy ()
// stejné jako očekávání (null) .toBeFalsy ()
očekávat ([1]). not.toEqual ([2])
  • toContain: zkontroluje, zda pole obsahuje prvek v parametru.
očekávat (['Apple', 'Banana', 'Strawberry']).
  • toThrow: zkontroluje, zda funkce vyvolá chybu
function connect () {
  hodit nové ConnectionError ()
}
očekávat (connect) .toThrow (ConnectionError)

Nejedná se pouze o zápasníky. Všechny zápasníky Jest najdete zde.

První testy

Nyní budeme psát náš první test a hrát si s našimi funkcemi. Nejprve vytvořte do adresáře soubor s názvem example.spec.js a vložte následující obsah:

description ('Example', () => {
  beforeAll (() => {
    console.log ('běží před všemi testy')
  })
  afterAll (() => {
    console.log ('běží po všech testech')
  })
  beforeEach (() => {
    console.log ('běží před každým testem')
  })
  afterEach (() => {
    console.log ('spuštěný po každém testu')
  })
  it ('Měl by něco udělat', () => {
    console.log ('první test')
  })
  it ('Měl by udělat něco jiného', () => {
    console.log ('druhý test')
  })
})

Upozorňujeme, že nemusíme importovat všechny funkce, které používáme. Jsou již poskytovány Jestem.

Spustit test příze:

Zkoumání různých testovacích funkcí

Protože ve vašich testech nemáte tvrzení, budou jen projít. Viděli jste různé příkazy console.log? Měli byste lépe porozumět tomu, jak nyní fungují vaše funkce háčků a testovací případy.

Nyní odeberte všechny funkce zavěšení a při prvním testu přidejte .skip:

description ('Example', () => {
  it.skip ('Měl by něco udělat', () => {
    console.log ('první test')
  })
  it ('Měl by udělat něco jiného', () => {
    console.log ('druhý test')
  })
})

Znovu spusťte test příze:

Je to logické, protože jste přeskočili test. První právě neběží.

Nyní přidejte třetí test do vaší testovací sady a použijte pouze.

description ('Example', () => {
  it ('Měl by něco udělat', () => {
    console.log ('první test')
  })
  it ('Měl by udělat něco jiného', () => {
    console.log ('druhý test')
  })
  it.only ('Měl by to udělat', () => {
    console.log („třetí test“)
  })
})

Spustit test příze ještě jednou:

Opět logické. Řeknete Jestovi, aby provedl pouze váš třetí test. Takže vidíte pouze třetí test v konzole.

Testování zásobníku s vývojem řízeným testem

Žádná další teorie. Čas na trénink.

V následujícím textu provedeme jednoduchou implementaci zásobníku v JavaScriptu s vývojem založeným na testech.

Připomínka, zásobník je datová struktura, přesněji struktura LIFO: Last In, First Out. Na zásobníku lze provést tři hlavní operace:

  • push: tlačí prvek v horní části stohu.
  • pop: odstraní prvek v horní části zásobníku.
  • peek: vrací poslední prvek v horní části zásobníku.

V našem případě vytvoříme třídu, jejíž název bude Stack. Aby věci byly složitější, budeme předpokládat, že tento zásobník má pevnou kapacitu. Zde jsou vlastnosti a funkce naší implementace zásobníku:

  • items: položky zásobníku. K implementaci zásobníku použijeme pole.
  • kapacita: kapacita zásobníku.
  • isEmpty (): vrací true, pokud je zásobník prázdný, jinak false.
  • isFull (): vrací true, pokud zásobník dosáhl své maximální kapacity, to znamená, když nemůžete posunout jiný prvek. Vrací nepravdivé jinak.
  • push (element): zatlačí prvek do zásobníku. Vrací plný, pokud je zásobník plný, prvek je posunut jinak.
  • pop (): odstraní poslední prvek zásobníku. Vrátí prázdné, pokud je zásobník prázdný, prvek je jinak vyskočen.
  • peek (): vrací prvek v horní části zásobníku (poslední stisknutý). Vrací prázdné, pokud je zásobník prázdný, vrátí prvek jinak.

Chystáme se vytvořit dva soubory stack.js a stack.spec.js. Použil jsem příponu .spec.js, protože jsem na to zvyklý, ale můžete použít .test.js nebo zadat jiné jméno a umístit jej pod __tests__.

Když vyvíjíme testem řízený vývoj, napišme neúspěšný test. Nejprve vyzkoušíme konstruktéra. Chcete-li soubor otestovat, musíte importovat soubor zásobníku:

const Stack = vyžadovat ('./ stack')

Pro ty, kteří se diví, proč jsem tu import nepoužil, je to proto, že nejnovější stabilní verze souboru Node.js ji nepodporuje dodnes. Mohl jsem přidat Babela, ale nechci tento tutoriál přetěžovat. Pojďme se tedy držet požadavku.

Jednou dobrou věcí, kterou musíte udělat při testování třídy nebo funkce, je spustit test popsáním souboru nebo třídy, kterou testujete. Tady jde o hromádku:

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

Potom musíme vyzkoušet, že při inicializaci zásobníku vytvoříme prázdné pole a nastavíme správnou kapacitu. Takže v popisném bloku píšeme následující test:

it ('Měl by sestavit zásobník s danou kapacitou', () => {
  let stack = new Stack (3)
  očekávat (stack.items) .toEqual ([])
  očekávat (stack.capacity) .toBe (3)
})

U stack.items používáme toEqual a ne toBe, protože neodkazují na stejné pole. Musíme tedy porovnat pouze jejich hodnoty.

Nyní spusťte test příze stack.spec.js. Jest provozujeme na konkrétním souboru, protože nechceme být znečištěni jinými testy. Zde je výsledek:

Stack není konstruktor. Samozřejmě. Stále jsme nevytvořili naši třídu Stack a poskytli jsme jí konstruktéra.

V stack.js vytvořte svou třídu, konstruktor a exportujte třídu:

stack stack {
  konstruktor () {
  }
}
module.exports = Stack

Proveďte test znovu:

Protože jsme ve konstruktoru nenastavili položky, Jest očekával, že se položky v poli budou rovnat [], ale nedefinovaly se. Poté musíte položky inicializovat:

konstruktor () {
  this.items = []
}

Pokud test spustíte znovu, dostanete stejnou kapacitu pro stejnou kapacitu, takže budete muset také nastavit kapacitu:

konstruktor (kapacita) {
  this.items = []
  this.capacity = kapacita
}

Spusťte náš test:

To jo! SLOŽIT. Viděli jste, jak jsme napsali řešení? O tom je TDD. Pokrývá váš kód kdykoli a umožňuje vám postupovat pomalu směrem k řešení při opravě neúspěšných testů. Doufám, že testování vám teď dává větší smysl! Pojďme tedy pokračovat?

je prázdný

Chcete-li otestovat isEmpty, chystáme se inicializovat prázdný zásobník, otestovat, zda isEmpty vrátí true, přidat prvek a znovu jej otestovat.

it ('Měl by mít funkci isEmpty, která vrací true, pokud je zásobník prázdný a v opačném případě false', () => {
  let stack = new Stack (3)
  očekávat (stack.isEmpty ()). toBe (true)
  stack.items.push (2)
  očekávat (stack.isEmpty ()). toBe (false)
})

Pokud spustíte test, měla by se zobrazit následující chyba:

TypeError: stack.isEmpty není funkce

Chcete-li tento problém vyřešit, budeme muset vytvořit isEmpty uvnitř třídy Stack:

je prázdný () {
}

Pokud spustíte test, měla by se zobrazit další chyba:

Očekávané: true
Přijato: nedefinováno

Dává smysl. Uvnitř isEmpty není přidáno nic. Zásobník je prázdný, pokud v něm nejsou žádné položky:

je prázdný () {
  vrátit this.items.length === 0
}

je plný

To je přesně to samé jako isEmpty, takže jako cvičení otestujte tuto funkci pomocí testem řízeného vývoje. Řešení najdete na samém konci tohoto tutoriálu.

Tam

Zde musíme vyzkoušet tři různé věci:

  • na horní část zásobníku by měl být přidán nový prvek.
  • push je "Full", pokud je zásobník plný.
  • prvek, který byl nedávno tlačen, by měl být vrácen.

Budeme vytvářet další blok pomocí popisu pro push. Vnořte tento blok do hlavního bloku popisu.

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

Přidání prvku

Abychom to otestovali, vytvoříme nový zásobník a zatlačíme prvek. Posledním prvkem pole položek by měl být právě přidaný prvek.

description ('Stack.push', () => {
  it ('Měl by se přidat nový prvek na horní část zásobníku', () => {
    let stack = new Stack (3)
    stack.push (2)
    očekávat (stack.items [stack.items.length - 1]) .Be (2)
  })
})

Pokud spustíte test, uvidíte, že push není definován, což je ještě jednou zcela normální. push bude potřebovat parametr, aby něco vtlačil do zásobníku:

push (element) {
 this.items.push (element)
}

Testy prošly znovu. Všimli jste si něco? Tento řádek vkládáme kopírováním:

let stack = new Stack (3)

Je to docela otravné. Naštěstí pro nás máme metodu beforeEach, která nám umožňuje provést nějaké nastavení před každým testovacím během. Proč tedy sestavit zásobník v této metodě?

nechat hromadu
beforeEach (() => {
 stack = new Stack (3)
})

Důležité: zásobník musí být deklarován před každým. Pokud ji definujete v metodě beforeEach, proměnná zásobníku nebude definována ve všech testech, protože není ve správném rozsahu.

Vedlejší poznámka: Pokud bychom už dříve použiliVšechno dříve, než každý, museli bychom také poskytnout metodu afterEach. Ve skutečnosti by instance zásobníku byla sdílena ve všech testech. To je problém, protože zatlačíme na zásobník, pop atd. Takže po každém testu bychom museli zásobník resetovat:

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

Ale tady, protože vytváříme novou instanci zásobníku před každým testem, tato metoda AfterEach není nutná, je však dobré to vědět.

Zpět na testování: Nyní můžete odebrat inicializaci zásobníku ve všech testech. Pro ty, kteří se cítí trochu ztraceni, je zde kompletní testovací soubor až do tohoto bodu:

const Stack = vyžadovat ('./ stack')
description ('Stack', () => {
  nechat hromadu
  beforeEach (() => {
    stack = new Stack (3)
  })
  it ('Měl by sestavit zásobník s danou kapacitou', () => {
    očekávat (stack.items) .toEqual ([])
    očekávat (stack.capacity) .toBe (3)
  })
  it ('Měl by mít funkci isEmpty, která vrací true, pokud je zásobník prázdný a v opačném případě false', () => {
    stack.items.push (2)
    očekávat (stack.isEmpty ()). toBe (false)
  })
  description ('Stack.push', () => {
    it ('Měl by se přidat nový prvek na horní část zásobníku', () => {
      stack.push (2)
      očekávat (stack.items [stack.items.length - 1]) .Be (2)
    })
  })
})

Testování vrácené hodnoty

Zde je test:

it ('Měl by vrátit nový prvek tlačený v horní části zásobníku', () => {
  let elementPushed = stack.push (2)
  očekávat (elementPushed) .toBe (2)
})

Po spuštění testu získáte:

Očekávané: 2
Přijato: nedefinováno

Vskutku. Uvnitř push se nic nevrací! Musíme to opravit:

push (element) {
  this.items.push (element)
  návratový prvek
}

Vrací plný, pokud je zásobník plný

V tomto testu musíme nejprve naplnit zásobník, stisknout prvek, ověřit, že na zásobník nebylo nic zatlačeno a že vrácená hodnota je plná.

it ('Měl by se vrátit plný, pokud se někdo pokusí tlačit na vrchol zásobníku, když je plný', () => {
  stack.items = [1, 2, 3]
  let element = stack.push (4)
  očekávat (stack.items [stack.items.length - 1]) .Be (3)
  expect (element) .toBe ('Full')
})

Tuto chybu byste měli dostat při spuštění testu:

Očekávané: 3
Přijato: 4

Takže prvek byl tlačen. To není to, co chceme. Než přidáme něco, musíme nejprve zkontrolovat, zda je zásobník plný:

push (element) {
  if (this.isFull ()) {
    návrat 'plný'
  }
  
  this.items.push (element)
  návratový prvek
}

Testy nyní probíhají. Skončili jsme s tlačením!

Cvičení: pop a peek

Myslím, že nyní získáte způsob, jak udělat testem řízený vývoj. Jako cvičení tedy otestujte a implementujte pop a peek.

Několik rad:

  • Pop je opravdu podobný push-moudrý.
  • Peek je opravdu podobný pop test-moudrý taky!
  • Až dosud jsme tento kód neaplikovali, protože ho nebylo třeba znovu upravovat. V těchto funkcích může existovat způsob, jak změnit kód po napsání testů a jejich provedení. Pokud upravíte kód, nemusíte mít obavy, testy jsou zde proto, aby vám daly vědět, co je špatně.

Nedívejte se na řešení níže, aniž byste to nejprve vyzkoušeli. Jediným způsobem, jak postupovat, je zkoušet, experimentovat a budovat věci.

Řešení

Jaké to bylo cvičení? Podařilo se vám? Doufám. Pokud tomu tak není, nebojte se, testování vyžaduje čas a úsilí a testy je těžké nejdříve napsat.

Když se podíváte na soubory, vidíte, že jsem použil ternární stav v popu a nahlédnutí. To je něco, co jsem refactored. Ve skutečnosti byla stará implementace:

if (this.isEmpty ()) {
  návrat 'Empty'
}
vrátit this.items.pop ()

Protože TDD nám umožňuje psát refaktor po napsání testů, našel jsem kratší implementaci bez obav z chování mých testů.

Testy spustíte ještě jednou:

Testován není pouze váš zásobník, ale kód jste také zdokumentovali opravdu dobře. Stačí se podívat na výsledky testů a okamžitě uvidíme, jak se váš stack chová.

Písemné testy hluboce zlepšují kvalitu kódu. Doufám, že nyní chápete sílu testování a vývoje založeného na testech.

Tento tutoriál je součástí mého nového kurzu: Sestavení a testování aplikace od nuly s Vue.js. Naučí vás vše, co potřebujete vědět, abyste mohli vytvořit skvělou aplikaci Vue: základy Vue, API, testování, styling, nasazení a další!

Pokud se vám tento tutoriál líbil, dejte mi vědět tím, že mi dáte nějaké tleskání. Vždy je skvělé vědět, že vaše práce je oceněna.