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 Pikachu et Salamèche
Quand je choisi une bataille entre Pikachu et Salamèche
Alors Pikachu 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.
Exercice 3
Bonus : Qui commence quand 2 Pokémons ont la même rapidité ?
Scénario 1 :
Quand un pokemon de type Éclair affronte un type Feu
Alors le pokemon Éclair double ses points d'attaques
Scénario 2 :
Quand un pokemon de type Éclair affronte un type Plante
Alors le pokemon Plante double ses points de vie
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();
})
Exercice 6
Testez au moins un composant qui faire une requête HTTP vers la pokéAPI
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
Exercice 7 Bonus
Testez le reste de votre application en choisi la méthode que vous voulez utiliser
TypeScript
Qu'est ce que cela apporte ?
TypeScript
Compilation
Puis...
Auto-Completion
et même...
Unit
Integration
E2E
Acceptance
Execution time
0
+∞
$
$$$
Compilation
Test Statiques
Test Statique
Les Linters !
Les Linters !
Les Linters !
Integration
E2E
Acceptance
Execution time
0
+∞
$
$$$
Static
Unit
Manuels
Les autres types de tests
Test E2E
Plusieurs Acteurs
- Selenium
- WebDriver.io
- Cypress
- PlayWright
- Nightwatch
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.
- 653