TDD - Parte ||

Test- driven development

Copertina articolo TDD

Tipi di test

Unit test

Il test unitario serve a testare una singola funzionalità e non deve avere nessuna dipendenza da altre classi.

Integration test

I test d’integrazione ci consentono di individuare i problemi che si verificano quando due unità si combinano per realizzare una funzionalità più complessa.

Functional test o E2e

Il test funzionale è definito come: "il test della funzionalità completa di alcune applicazioni".
Esso simula l'interazione dell'utente reale su una pagina Web. Risulta però molto più complesso da realizzare e mantenere.

Google suggerisce spesso una divisione del 70/20/10: 70% di test unitari, 20% di test di integrazione e 10% di test end-to-end.
Questa regola è sicuramente da tenere a mente anche se il giusto equilibrio dipende dalla natura del progetto.

Immagine suddivisione fasi

Framework 

I test si possono scrivere da soli oppure utilizzando alcuni framework capaci di semplificare molto il lavoro.
Non è consigliato scriverli autonomamente perché questo richiede molto tempo e  risulta  molto difficile dalla parte di gestione. Analizziamo meglio quanto descritto attraverso un esempio.
const remove = (x,y) => {
 return x - y;
}

Se volessimo testare questa semplice funzione dovremmo scrivere una cosa del genere:
if(remove(2, 1) == 1) {
  console.log("Passed the test !!");
} else {
  console.log("Failed the test !!");
}


Come possiamo osservare è necessario molto tempo per scrivere test per più funzioni.
Essi non sono molto chiari da leggere perché risulta difficile comprendere quali test abbiamo superato e quali no, quale sia la parte dannosa del codice, quanti test abbiamo ecc.
I framework ci vengono in soccorso per evitare tutto questo, ci permettono di scrivere facilmente i test e i risultati sono di facile lettura.
I più utilizzati e famosi nel mondo Javascript sono: Mocha, Jasmin, Jest/Enzyme, Cypress.
Procediamo ad analizzare ognuno di essi in modo più specifico concentrandoci sulle loro caratteristiche.

Mocha

  • Funziona sia per il frontend che per il backend
  • Supporta il debugger NodeJS
  • Possiede un' ottima documentazione e parte di supporto
  • Supporta qualsiasi browser

Esempio di test:

var assert = require('assert');
describe(‘Math’, function() {
  it('should return 0', function(){
    assert.equal(0, 3 % 3);
  });
});

Nella prima riga del nostro test importiamo una libreria di asserzione.La libreria di asserzione è uno strumento capace di controllare che quanto inserito sia corretto e quindi di verificare effettivamente i risultati del test.
Possiamo utilizzare qualsiasi libreria di asserzioni desideriamo.
Il loro utilizzo non è obbligatorio ma serve a semplificare il test.
La libreria  mette a disposizione diversi metodi. Possiamo decidere quali vogliamo scegliere.
Nell'esempio appena riportato, abbiamo utilizzato: assert.equal(actual, expected).

  • assert.equal(actual, expected) ci consente di verificare l'uguaglianza dei parametri passati.
  • describe è un modo per raggruppare i nostri test. Accetta due argomenti: il primo è il nome del gruppo dei test mentre il secondo è una funzione di callback.
  • it identifica il singolo test e prevede due argomenti: una stringa che spiega cosa dovrebbe fare il test e una funzione di callback che contiene il nostro effettivo test.
    it('should return 0', function(){
      assert.equal(0, 3 % 3);
    });

    Facendo partire il nostro test otterremmo questo risultato: 

Math
  ✓ should not return rest

1 passing (9ms)

Tradotto indicherebbe: 

  • Nome del nostro gruppo di test
  • Messaggio del test 
  • Quanti test abbiamo passato

Ecco alcuni link utili Mocha:

JASMINE

  • Non dipende da nessun altro framework javascript
  • Non richiede DOM
  • Tutta la sintassi è chiara e ovvia
  • È una framework opensource disponibile in varie versioni standalone, ruby, node ecc.

Come prima cosa dobbiamo scaricare la versione di Jasmine che preferiamo da questo link e importare il contenuto nel nostro progetto.Quindi la struttura del nostro progetto dovrebbe avere questo aspetto.

 I contenuti delle cartelle spec e src necessitano di essere cancellati perché sono soltanto esempi.

Esempi

Vediamo ora un semplice esempio di come creare un test con Jasmine.
Creiamo un file che chiamato HelloWord.js dentro /src e all’interno riportiamo questa semplice funzione.
function helloWorld() {
  return 'Hello world!';
}

Successivamente creiamo un file di nome HelloWordSpec.js  dentro /spec  e scriviamo il nostro test.
describe('Hello world', function () {
  it('says hello', function () {
    expect(helloWorld()).toEqual('Hello world!');
  });
});

describe: è ciò che chiamiamo suite. Possiamo definirlo come un componente dell'applicazione, una classe o una serie di funzioni. Questa funzione  prevede due argomenti: il primo è il titolo della suite mentre il secondo è la callback.
it: è anche questa una funzione JavaScript che indica cosa dovrebbe fare un pezzo di codice. Prevede due argomenti: il  titolo (il primo) e la callback (il secondo).
Nel nostro esempio ci concentriamo sul verificare se il risultato della funzione  helloWorld () equivalga effettivamente a "Hello world!".
Questo tipo di controllo si chiama matcher.
Esistono molteplici matcher e anche noi siamo abilitati a definirne uno di essi.
Grazie a questo link è possibile analizzare nel dettaglio ogni matcher e qualora lo si desideri crearne anche uno custom.
Una volta definiti i nostri test è necessario importarli nel file SpecRunner.html .

<!-- include source files here... -->
<script src="src/HelloWord.js"></script>

<!-- include spec files here... -->
<script src="spec/HelloWordSpec.js"></script>

Aprendo il file, ci apparirà una schermata come questa dove potremo osservare i risultati dei nostri test.

Immagine link utili Jasmine

Link utili Jasmine:

CYPRESS

Questo framework, oltre a consentire di scrivere unit test e functional test, ci permette anche di scrivere End to End test e, quindi, testando il cosiddetto "flusso utente" verificare se un'applicazione Web funzioni come previsto o meno.

I test end to end non piacciono a molti perché  possono risultare lenti, ingombranti e costosi da scrivere.
Allo stesso tempo però essi possono facilitare il lavoro.
Il loro vero punto di forza è che consentono di combinare insieme tutti i tipi di test (unit, functional, end to end).
I punti di forza di Cypress sono:

  • Velocità del Setup iniziale
  • Velocità  nella fase di implementazione e durante quella esecutiva 
  • Rende possibile combinare tutti i tipi di test
  • La vastità della documentazione e community intorno ad esso 

Installazione Cypress

1.Creiamo una cartella e ci inizializziamo un nuovo progetto.

mkdir cypress-tutorial && cd $_
npm init -y

2. Installiamo Cypress nel nostro progetto

npm i cypress --save-dev
3.Avviamo Cypress
node_modules/.bin/cypress open

Saremo di fronte a molte nuove cartelle con esempi che possiamo, qualora lo volessimo, eliminare.
4. Creiamo poi un nuovo file all’interno di cypress/integration/nomeFile.js  e iniziamo a scrivere il nostro test.

describe("Input form ", () => {
 beforeEach(() => {
    cy.visit('http://localhost:3000')
})
it("focus input on load", () => {
  cy.focused().should('have.class', 'new-todo')
 })
it("accepts input", () => {
    const typeText = "Milk";
    cy.get(".new-todo").type(typeText).should("have.value",
typeText);
 })
})

Come possiamo notare il fatto che la sintassi sia molto simile ai precedenti framework.
- Describe or Context è il contenitore dei test.

Quando iniziamo un nuovo test dobbiamo sempre iniziare con questo metodo che prevede due parametri:

  • Una stringa per la descrizione della suite di test
  • Una funzione di callback cioè il test effettivo.

- It è il blocco che contiene il test.
- beforeEach esegue il codice al suo interno per ogni test all’interno dello stesso scope, quindi nel nostro caso verrà eseguito 2 volte.
Cypress ci offre altri hooks simili a questo come: after, before, afterEach.
cy
è Cypress stesso che offre diversi metodi e in questo caso stiamo dicendo: 

- Metti il focus quando carichiamo la pagina(.focused) all’elemento con classe .
- New-todo(.should(“have.class”)) successivamente settiamo all’elemento.
- New-todo il valore “Milk” con (.type , che simula la pressione del tasto enter.) e controlliamo che il valore sia uguale a  “Milk”(.should(“have.value”))
Per avviare il test andiamo prima nel package.json e creiamo un nuovo script chiamato e2e. 
"scripts": {
     "e2e": "cypress open"
}

Avviamo la nostra applicazione e dopo avviamo Cypress.
npm run e2e

Il risultato sarà il seguente:

Immagine risultato ottenuto
Ecco alcuni link utili Cypress:

JEST / ENZYME

Jest è un framework sviluppato da Facebook per testare codice javascript, quindi molto indicato quando si vuole testare codice scritto in React
Può essere utilizzato anche con typescript e react native  e non necessita  di alcuna configurazione iniziale.
Di solito viene usato in combinazione Enzyme, un utility javascript, gestita da Airbnb, che permette di rendere più facile la manipolazione dei componenti e semplificare la stesura del test.
Ecco i fattori positivi:

  • Isolamento dei test per permetterne il run in parallelo
  • Molto ben documentato e larga community
  • Nessuna configurazione

1. Per prima cosa installiamo jest e enzyme nel nostro progetto.

npm install --save-dev jest enzyme jest-enzyme
enzyme-adapter-react-16

 Andiamo nel nostro package.json e creiamo il nostro script per lanciare i test.

"scripts": {
    "test": "jest"
  },

Tutti i nostri test per convenzione li mettiamo all’interno di una cartella di denominata  __tests__ , i file dei test possono avere sia estensione .test.js che .spec.js.
describe(‘MyComponent’, () => {
  it('should show text', () => {
    const wrapper = shallow(MyComponent);
    const text = wrapper.find('div div')
    expect(text.text()).toBe('Text goes here')
  });
  it('should hide text when button is clicked', () => {
    const wrapper = shallow(MyComponent);
    const button = wrapper.find('button');
    button.simulate('click')
    const text = wrapper.find('div div')
    expect(text.length).toBe(0)
  })
});

Anche qui come gli negli altri framework la sintassi è quasi sempre la stessa.
Describe è il wrapper del nostro test e it,  ossia il test stesso che prevede due valori:

  • Il primo  input: un testo cioè il nome (che dovrebbe far capire cosa fa il test)
  • Il secondo parametro ossia una funzione, il test vero e proprio.

All’interno del  nostro test abbiamo utilizzato il metodo shallow che è un api offerta da enzyme, utile per testare un componente come singola unità, cioè  non renderizza i figli e quindi si sa per certo sai per certo che l'errore, se presente, viene da questo componente.
Poi andiamo a cercare all’interno del nostro componente un div contenuto in un altro div e controlliamo che il valore del testo sia quello che ci aspettiamo.
Il secondo test controlla che il testo venga nascosto dopo il click sul bottone, prende il componente, cerca il bottone, simula un click e alla fine si aspetta che il la lunghezza del testo sia uguale a 0.

Puoi trovare:
- Elenco delle api di enzyme qui.
- Elenco expect di jest (ci forniscono una serie di matchers che servono per controllare che i valori soddisfano determinate condizioni) qui.
- Elenco api utili  qui.

Se desideri approfondire: Contattaci