Test

@berthel350

Programme

Jour 1

- Rappel sur l’écosystème JS
- Mise en pratique de plusieurs méthodologies de Tests.

Jour 2

- Le reste des méthodologies de test
- Les différents types de tests
- Mise en pratique avec un  framework front-end.

Travaux Pratiques

 ⚠️ Pas de corrections

 ✅ Posez des questions

 ✅ Demandez moi une revue de code

Florent Berthelot

  • 10 ans d'expériences très diverses autour de l'écosystème JavaScript
  • 2 SSII (ESN), puis indépendant (WeFacto)

JavaScript

Questions Pratiques

  • 9h - 12h30
  • 14h - 17h30
  • Une pause par demi journée

Rappels JS

Outillage JS

Méthodologie I

Pas de test !

Échauffement

Codez une méthode qui retourne la rapidité d'un Pokemon


Chaque Pokémon a une vitesse initial.

La rapidité = vitesse initial + (niveau du Pokemon / génération du Pokémon)


Votre programme affiche la rapidité de Pikachu, Salamèche et Bulbizare.

⚠️ Pas le droit de lancer le programme ⚠️

Méthodologie II

Test After

Coder

Implémenter le test

Le test fonctionne

Refactor

@berthel350

Test After

Qu'en pensez-vous ?

Framework de test

Jasmine + Karma

Karma

Jasmine

Console

@berthel350

Jest

Jest

JS DOM

Console

@berthel350

L'installation Jest

$ npm install --save-dev jest
// Package.json

{
  "scripts": {
    "test": "jest"
  }
}
$ npm run test

Tous les fichiers, qui termine par .spec.js, .test.js ainsi que ceux dans des dossiers __tests__ seront considéré comme des tests

JEST & TypeScript

$ npm i -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest
// babel.config.js

module.exports = {
  presets: [
    ['@babel/preset-env', {targets: {node: 'current'}}],
    '@babel/preset-typescript',
  ],
};

Structure de test

import {add, sub} from 'math';

describe('Math', () => {
  describe('add', () => {
    it('should return 0 when add 0 to 0', () => {
      expect(add(0, 0)).toBe(0);
    });
    
    it('should return 1 when add 0 to 1', () => {
      expect(add(1, 0)).toBe(1);
    })
    
    it('should return 3.14 when add 0.14 to 3', () => {
      expect(add(3, 0.14)).toBe(3.14);
    })
  });
  
  describe('sub', () => {
    it('should return 0 when sub 0 to 0', () => {
      expect(sub(0, 0)).toBe(0);
    });
    
    it('should return 1 when sub 0 to 1', () => {
      expect(sub(1, 0)).toBe(1);
    })
    
    it('should return 3.14 when sub 0.86 to 4', () => {
      expect(sub(4, 0.86)).toBe(3.14);
    })
  });
});

Librairie d'assertion

Globals

Vitest

jest.fn => vitest.fn

 

Et c'est plus rapide !

Vitest

Jest = CommonJS

 

Vitest = ES Modules

Exercice 1

- Ajoutez Jest ou Vitest à votre projet
- Testez
- Refactor

Méthodologie III

Test First

Implémenter le test

Coder

Le test fonctionne

@berthel350

Test First

Qu'en pensez-vous ?

Méthodologie IV

Spec driven dev

Écrire le it.todo

Coder

implémenter le test

Le test passe

@berthel350

Spec Driven Development

Qu'en pensez-vous ?

Refactor

Le test passe encore

Spec Driven Development

Scénario 1: Bataille de pickachu et salamèche


Quand je choisi une bataille entre Pickachu et Salamèche
Alors pickachu gagne

Spec Driven Development

Scénario 1: Bataille de pikachu et salamèche


Quand je choisi une bataille entre Pikachu et Salamèche
Alors pikachu est le vainqueur

describe('Battle', () => {
  it.todo('should design pikachu as a winner when pikachu fight against salameche');
});

Spec Driven Development

Scénario 2: Bataille de pikachu et salamèche


Quand je choisi une bataille entre Pikachu et Salamèche
Alors pikachu gagne un niveau

describe('Battle', () => {
  it.todo('should make pikachu a winner when pikachu fight against salameche');
  
  it.todo('should make pikachu level up when pikachu fight against salameche');
});

Spec Driven Development

describe('Battle', () => {
  describe('when pikachu fight against salamech', () => {
    beforeEach(() => {
		// Initialize pikachu and salameche battle here
    });

    it.todo('should make pikachu a winner');

    it.todo('should make pikachu level up');
  });
});

Exercice 2

Implémentez les scénarios
en Spec Driven Developement

Scénarios

Scénario 1:
   Quand Pikachu et Salamèche s'affrontent
   Alors Pikachu est le premier à attaquer

 

Scénario 2:
   Quand Pikachu et Salamèche s'affrontent
   Alors Pikachu gagne

 

Scénario 3:
   Quand Pikachu et Bulbizarre s'affrontent
   Alors Bulbizarre gagne

 

Scénario 4:
   Quand Pikachu et Bulbizarre s'affrontent
   Alors Pikachu est le premier à attaquer

@berthel350

Méthodologie V

Test Driven Development

Écrire le test

Le test échoue

Faire passer le test

Refactor

Le test passe encore

@berthel350

Exercice 3

Maintenant les Pokemons ont un type (Feu, Éclair, Plante, ...) qui impacte leurs attaques et leurs défenses.

Bonus : Que se passe t'il en cas d'égalité ?

Mock, Stub, Spy ?

Qu'est ce que c'est ?

@berthel350

L'exemple

@berthel350

import {getUser} from './user-service';
import {info} from './logger'



export const findFriend = async (userId) => {
	try {
    	const user = await getUser(userId)
        return user.friends;
    } catch(err) {
    	console.error(err);
        return undefined;
    }
}

Dummy !

@berthel350

const loggerDummy = () => {
  throw new Error('Dummy')
}

Stub

@berthel350

const getUserStub = () => {
  return Promise
    .resolve({friends: []})
}

const getUserStub = jest.fn(() => {
  return Promise
    .resolve({friends: []})
})

Mock

@berthel350

const getUserMock = jest.fn((userID) => {
  if(userId === 'flo') {
    return Promise
    	.resolve({friends: ['martinique']})
  }
  
  
  return Promise
	.reject(new Error('Remi sans amis, sans famille'))
})

Spies

@berthel350

jest.spyOn(console, 'error');

expect(console.error).toHaveBeenCalled();

Jest.fn

@berthel350

jest
  .spyOn(console, 'error')
  .mockImplementation(() => {});

const getUserStub = jest.fn(() => {
  return Promise
    .resolve({friends: []})
})

expect(getUserStub).toHaveBeenCalled();

L'exemple

@berthel350

import {getUser} from './user-service';
import {info} from './logger'



export const findFriend = async (userId) => {
    try {
    	const user = await getUser(userId)
        return user.friends;
    } catch(err) {
    	console.error(err);
        return undefined;
    }
}

Jest

@berthel350

import {getUser} from './user-service';

jest.mock('./user-service', () => {
  return {
    getUser: jest.fn()
  }
})

describe('findFriend', () => {
  beforeEach(() => {
    jest.spyOn(console, 'error').mockImplementation(() => {});
  })
  afterEach(() => {
    console.error.mockRestore();
    // (console.error as jest.Mock).mockRestore();
  })

  it('should find florent friend', async () => {
      getUser.mockImplementation(() => {
        return Promise.resolve({friends: ['martinique']})
      })
    
      const result = await findFriend('flo')
      expect(result).toEqual(['martinique'])
  })

  describe('when trying to find remi friend', () => {
    beforeEach(() => {
      getUser.mockImplementation(() => {
        return Promise.reject(new Error('oops'))
      })
    });
    
    it('should log error when find remi friend', async () => {
      const result = await findFriend('remi')
      expect(console.error).toHaveBeenCalledWith('oops');
    })

    it('should return undefined', async () => {
      const result = await findFriend('remi')
      expect(result).toBe(undefined);
    })
  })
})

Exercice 4

Les rapidité des Pokémons sont issues d'un appel de la Poké API.

Pour cet exercice, il faut Mocker (ou Stuber) l'appel réseau.

Les différents types de test

Test unitaire

Confiné en mémoire (RAM)

Test d'integration

Test déconfiné

Possibilité d'accéder aux fichiers, au réseau, ...

Test de bout en bout

Interdit de bouchonner

Comme pour le vin, il est interdit d'utiliser des mocks, des spys, ...

ISO prod

Test d'acceptance

Valide mon travail

Si ce test passe alors ma fonctionnalité est développée

 

Gerkin, Cucumber, ...

Test d'acceptance

Valide mon travail

Si ce test passe alors ma fonctionnalité est développée

 

Gerkin, Cucumber, ...

Test d'acceptance

Valide mon travail

Feature: Subscribers see different sets of stock images based on their subscription level
 
Scenario: Free subscribers see only the free articles
  Given Free Frieda has a free subscription
  When Free Frieda logs in with her valid credentials
  Then she sees a Free article on the home page

Scenario: Subscriber with a paid subscription can access both free and paid articles
  Given Paid Patty has a basic-level paid subscription
  When Paid Patty logs in with her valid credentials
  Then she sees a Free article and a Paid article on the home page

Test d'acceptance

Valide mon travail

Méthodologie VI

Behavior Driven Development

@berthel350

Écrire le test

Le test échoue

Faire passer le test

Refactor

Le test passe encore

Coder test d'acceptance

@berthel350

ATDD / BDD

Qui écrit les test ?

Product Owner => BDD
Les Devs => ATDD

Unit

Integration

E2E
 

Acceptance

Execution time

0

+

$

$$$

Les Anti-pattern

Unit

integration

E2E

acceptance

@berthel350

Unit

Integration

E2E
 

Execution time

0

+

$

$$$

D'autres modèle

Outside Diamond, ...

Exercice 5

Les batailles de Pokémons s'affichent sur une page Web

Mais avant, définissons ensemble quel est la stratégie de test pour ce projet.

Comment tester un composant

@berthel350

Live-Coding

@berthel350

Choisissez le Framework Front et de test :)

Trucs et Astuces

Le HTML est versatile, restez vague dans les assertions

it('should display the captain\'s age', () => {
  render(<MyComponent />)

  expect(screen.getRole('heading'))
    .toHaveTextContent('21 ans et toutes ses dents !')
})
it('should display the captain\'s age', () => {
  render(<MyComponent />)

  expect(screen.getByText('21 ans et toutes ses dents !'))
    .toBeVisible()
})

Trucs et Astuces

Se mettre à la place d'un utilisateur qui voit TOUT en slow motion

it('should display the captain\'s age', async () => {
  render(<MyComponent />)

  await waitForElementToBeRemoved(() => screen.queryByText('loading...'));

  expect(screen.getByText('21 ans et toutes ses dents !')).toBeVisible();
})

Les stratégies !

London - Outside-In

London - Outside-In

C

C

C

C

C

C

C

C

C

Chicago - Inside-out

C

C

C

C

C

C

C

C

C

TypeScript

Qu'est ce que cela apporte ?

TypeScript

Compilation

Puis...

Auto-Completion

et même...

Unit

Integration

E2E
 

Acceptance

Execution time

0

+

$

$$$

Compilation

Les autres types de tests

Test E2E

Plusieurs Acteurs

Cypress

Cypress

Mutation Testing

Test de charge

Test de performance

Comment développer en équipe ?

Mob Programming

Tout le monde sur un seul ordinateur.

Pomodoro

Toutes les X minutes, celui qui a le clavier change.

Strong-Style

 

Celui qui écrit c'est celui qui ne pense pas.

Dev2

Ordi

Dev1

Dev3

Attaque - Défense

L'un code des tests pour piéger celui qui code.

Git ou autre s'adapte à vos méthodologies

Conclusion

Attention au dogmes !

PS: ceci est un dogme 🙃

Conclusion

florent@berthelot.io

berthelot.io
@FBerthelot sur GitHub
@Berthel350 sur GitHub
 

Bonus

Design-System

Automate all the things

En max 3 commandes, je peux dev
 

Toutes ce qui est lancé sur la CI/CD peut être lancé sur mon poste

JavaScript Craftmanship

By Florent Berthelot

JavaScript Craftmanship

Vous trouvez que le Test Driven Development est une utopie en front ? Que le TDD est réservé aux tests côté back-end ? Laissez-moi vous montrer qu'avec un peu de pratique, nous allons tout a fait pouvoir faire du Test-First sur Angular. Nous verrons alors comment progressivement améliorer notre manière de travailler pour faire du TDD et pourquoi pas même faire du Behavior Driven Development ! Les tests étant un investissement au même titre que le code application, nous veillerons à ce qu'ils soient le plus maintenable possible.

  • 494