Ale opravdu, co je test JavaScriptu?

Testování softwaru. Existuje spousta důvodů, proč to děláme. Tady jsou dva moje:

  1. Urychluje můj pracovní postup, takže mohu rychleji vyvíjet software
  2. Pomáhá mi zajistit, že při provádění změn neporuším stávající kód

To znamená, že mám pro vás několik otázek (toto jsou průzkumy na Twitteru):

  • Psali jste někdy test JavaScript?
  • Použili jste někdy testovací rámec JavaScriptu?
  • Nastavili jste někdy úplně od začátku testovací rámec JavaScriptu?
  • Rozumíte testovacím rámcům natolik dobře, abyste mohli implementovat své vlastní? (dokonce opravdu jednoduchý?)

Cílem tohoto příspěvku je přimět každého z vás, aby na poslední otázku mohl odpovědět „Ano“. Máte tedy základní znalosti o tom, co je to test v JavaScriptu, který vám pomůže psát lepší testy.

Takže to, co uděláme, je tento jednoduchý math.js modul a napsat testy pro dvě funkce, které odhaluje:

const sum = (a, b) => a + b
const subtract = (a, b) => a - b

module.exports = {sum, subtract}

Udělal jsem repo na GitHubyou, na který se mohu také odkazovat 🐙😸

Krok 1

Zde je nejzákladnější forma testu, která mě napadá:

// basic-test.js
const actual = true
const expected = false
if (actual !== expected) {
  throw new Error(`${actual} is not ${expected}`)
}

Tento testovací kód můžete spustit spuštěním node basic-test.js ! To je test! 🎉

Test je kód, který vyvolá chybu, když skutečný výsledek něčeho neodpovídá očekávanému výstupu. Může to být komplikovanější, když se zabýváte kódem, který závisí na nějakém stavu, který je třeba nejprve nastavit (např. komponenta musí být vykreslena do dokumentu, než budete moci spouštět události prohlížeče, nebo musí být uživatelé v databázi). Je však relativně snadné testovat „čisté funkce“, jako jsou ty v našem math.js modul (funkce, které vždy vrátí stejný výstup pro daný vstup a nemění stav okolního světa).

Část, která říká actual !== expected se nazývá „tvrzení.“ Je pryč říkat v kódu, že jedna věc by měla mít určitou hodnotu nebo projít určitým...eh...testem :) Může to být tvrzení, že actual odpovídá regulárnímu výrazu, je anarray s určitou délkou nebo libovolným počtem věcí. Klíčem je, že pokud naše tvrzení selže, vyvoláme chybu.

Zde je tedy nejzákladnější test pro náš math.js funkce:

// 1.js
const {sum, subtract} = require('./math')

let result, expected

result = sum(3, 7)
expected = 10
if (result !== expected) {
  throw new Error(`${result} is not equal to ${expected}`)
}

result = subtract(7, 3)
expected = 4
if (result !== expected) {
  throw new Error(`${result} is not equal to ${expected}`)
}

Tady máš! Spusťte to pomocí node a příkaz bude ukončen bez chyby. Nyní přerušme sum funkce změnou + na - a spusťte to znovu a uvidíme:

$ node 1.js
/Users/kdodds/Desktop/js-test-example/1.js:8
  throw new Error(`${result} is not equal to ${expected}`)
  ^

Error: -4 is not equal to 10
    at Object.<anonymous> (/Users/kdodds/Desktop/js-test-example/1.js:8:9)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

Chladný! Už teď těžíme z našich základních testů! Nemůžeme prolomit sum fungovat bez porušení našeho automatického testu! Neato!

Jednou z nejdůležitějších částí testovacích rámců (nebo knihoven asercí) je užitečnost jejich chybových zpráv. Často, když test selže, první věc, kterou uvidíte, je chybová zpráva. Pokud z chybové zprávy nemůžete zjistit, jaký je základní problém, musíte strávit několik minut prohlížením kódu, abyste pochopili, co se pokazilo. Kvalita chybové zprávy do značné míry závisí na tom, jak dobře rozumíte a používáte tvrzení poskytovaná rámcem, který používáte.

Krok 2

Věděli jste, že Uzel ve skutečnosti hasanassert modul pro vytváření tvrzení, jako je ten, který máme výše 🤔? Pojďme refaktorovat náš test, abychom tento modul použili!

// 2.js
const assert = require('assert')
const {sum, subtract} = require('./math')

let result, expected

result = sum(3, 7)
expected = 10
assert.strictEqual(result, expected)

result = subtract(7, 3)
expected = 4
assert.strictEqual(result, expected)

Pěkný! Toto je stále testovací modul. To je funkčně ekvivalentní tomu, co jsme měli dříve. Jediný rozdíl je chybová zpráva:

$ node 2.js
assert.js:42
  throw new errors.AssertionError({
  ^

AssertionError [ERR_ASSERTION]: -4 === 10
    at Object.<anonymous> (/Users/kdodds/Desktop/js-test-example/2.js:8:8)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

Všimnete si, že vyvolaná chyba již neobsahuje žádný náš vlastní kód, což je škoda... 😦 Ale pojďme dál.

Krok 3

Pojďme si napsat vlastní jednoduchý testovací „rámec“ a knihovnu asercí. Začneme knihovnou asercí. Takže místo vestavěného assert Node modulu vytvoříme knihovnu, kterou nazveme expect . Zde je náš refaktorovaný test s touto změnou:

// 3.js
const {sum, subtract} = require('./math')

let result, expected

result = sum(3, 7)
expected = 10
expect(result).toBe(expected)

result = subtract(7, 3)
expected = 4
expect(result).toBe(expected)

function expect(actual) {
  return {
    toBe(expected) {
      if (actual !== expected) {
        throw new Error(`${actual} is not equal to ${expected}`)
      }
    },
  }
}

Skvělé, takže teď můžeme přidat spoustu výrazů k objektu, který vrátíme (jako toMatchRegex nebo toHaveLength ). A tady je chybová zpráva:

$ node 3.js
/Users/kdodds/Desktop/js-test-example/3.js:17
        throw new Error(`${actual} is not equal to ${expected}`)
        ^

Error: -4 is not equal to 10
    at Object.toBe (/Users/kdodds/Desktop/js-test-example/3.js:17:15)
    at Object.<anonymous> (/Users/kdodds/Desktop/js-test-example/3.js:7:16)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

Dobře, věci vypadají dobře.

Krok 4

Ale teď je tu problém 😖... Pokud vidím tu chybovou zprávu, jak poznám, že sum funkce je ta, která je rozbitá? Může to být subtract modul. Zdroj testu také nedělá dobrou práci při udržování testů izolovaných (vizuálně nebo jinak).

Pojďme tedy napsat pomocnou funkci, aby to fungovalo:

// 4.js
const {sum, subtract} = require('./math')

test('sum adds numbers', () => {
  const result = sum(3, 7)
  const expected = 10
  expect(result).toBe(expected)
})

test('subtract subtracts numbers', () => {
  const result = subtract(7, 3)
  const expected = 4
  expect(result).toBe(expected)
})

function test(title, callback) {
  try {
    callback()
    console.log(`✓ ${title}`)
  } catch (error) {
    console.error(`✕ ${title}`)
    console.error(error)
  }
}

function expect(actual) {
  return {
    toBe(expected) {
      if (actual !== expected) {
        throw new Error(`${actual} is not equal to ${expected}`)
      }
    },
  }
}

Nyní můžeme vložit vše, co je relevantní pro daný test, do naší "testovací" callback funkce a můžeme tomuto testu dát jméno. Potom použijeme test funkce, která nejen poskytne užitečnější chybovou zprávu, ale také spustí všechny testy v souboru (bez kauce při první chybě)! Zde je výstup:

$ node 4.js
✕ sum adds numbers
Error: -4 is not equal to 10
    at Object.toBe (/Users/kdodds/Desktop/js-test-example/4.js:29:15)
    at test (/Users/kdodds/Desktop/js-test-example/4.js:6:18)
    at test (/Users/kdodds/Desktop/js-test-example/4.js:17:5)
    at Object.<anonymous> (/Users/kdodds/Desktop/js-test-example/4.js:3:1)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
✓ subtract subtracts numbers

Bonbón! Nyní vidíme samotnou chybu a vidíme název testu, takže víme, který z nich máme opravit.

Krok 5

Takže vše, co nyní musíme udělat, je napsat nástroj CLI, který vyhledá všechny naše testovací soubory a spustí je! Tento kousek je zpočátku docela jednoduchý, ale existuje spousta věcí, které k němu můžeme přidat. 😅

V tuto chvíli budujeme testovací rámec a testovací běžec. Naštěstí pro Forus, existuje spousta těchto postavena již! Vyzkoušel jsem jich tuny a všechny jsou skvělé. To znamená, že nic se nepřibližuje mým případům použití lépe než Jest 🃏. Je to úžasný nástroj (více o Jestovi se dozvíte zde).

Takže místo toho, abychom budovali vlastní framework, pojďme do toho a přepněte náš test na práci s Jest. Jak to tak bývá, už se to děje! Jediné, co musíme udělat, je odstranit naši vlastní implementaci test a expect protože Jest je zahrnuje do našich testů jako globální objekty! Takže teď to vypadá takto:

// 5.js
const {sum, subtract} = require('./math')

test('sum adds numbers', () => {
  const result = sum(3, 7)
  const expected = 10
  expect(result).toBe(expected)
})

test('subtract subtracts numbers', () => {
  const result = subtract(7, 3)
  const expected = 4
  expect(result).toBe(expected)
})

Když spustíme tento soubor pomocí Jest, výstup vypadá následovně:

$ jest
 FAIL  ./5.js
  ✕ sum adds numbers (5ms)
  ✓ subtract subtracts numbers (1ms)

● sum adds numbers

expect(received).toBe(expected)

    Expected value to be (using Object.is):
      10
    Received:
      -4

      4 |   const result = sum(3, 7)
      5 |   const expected = 10
    > 6 |   expect(result).toBe(expected)
      7 | })
      8 |
      9 | test('subtract subtracts numbers', () => {

      at Object.<anonymous>.test (5.js:6:18)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.6s, estimated 1s
Ran all test suites.

Z textu to není poznat, ale ten výstup je barevný. Zde je obrázek výstupu:

Má barevné kódování, které je opravdu užitečné při identifikaci částí, které jsou relevantní 😀 Ukazuje také kód, kde došlo k chybě! Nyní to je užitečná chybová zpráva!

Závěr

Co je tedy test JavaScriptu? Je to prostě nějaký kód, který nastaví nějaký stav, provede nějakou akci a provede tvrzení o novém stavu. Nemluvili jsme o běžných pomocných funkcích frameworku, jako je beforeEach nebodescribe a existuje mnoho dalších tvrzení, která bychom mohli přidat jakotoMatchObject nebotoContain .Doufáme, že vám to poskytne představu o základních konceptech testování pomocí JavaScriptu.

Doufám, že vám to pomůže! Hodně štěstí! 👍