@berthel350
- Rappel sur l’écosystème JS
- Mise en pratique de plusieurs méthodologies de Tests.
- Le reste des méthodologies de test
- Les différents types de tests
- Mise en pratique avec un framework front-end.
JavaScript
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 ⚠️
Coder
Implémenter le test
Le test fonctionne
Refactor
@berthel350
Qu'en pensez-vous ?
Karma
Jasmine
Console
@berthel350
Jest
JS DOM
Console
@berthel350
$ 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
$ 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',
],
};
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);
})
});
});
jest.fn => vitest.fn
Et c'est plus rapide !
Jest = CommonJS
Vitest = ES Modules
- Ajoutez Jest ou Vitest à votre projet
- Testez
- Refactor
Implémenter le test
Coder
Le test fonctionne
@berthel350
Qu'en pensez-vous ?
Écrire le it.todo
Coder
implémenter le test
Le test passe
@berthel350
Qu'en pensez-vous ?
Refactor
Le test passe encore
Scénario 1: Bataille de Pikachu et Salamèche
Quand je choisi une bataille entre Pikachu et Salamèche
Alors Pikachu gagne
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');
});
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');
});
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');
});
});
Implémentez les scénarios
en Spec Driven Developement
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
Écrire le test
Le test échoue
Faire passer le test
Refactor
Le test passe encore
@berthel350
Maintenant les Pokemons ont un type (Feu, Éclair, Plante, ...) qui impacte leurs attaques et leurs défenses.
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
@berthel350
@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;
}
}
@berthel350
const loggerDummy = () => {
throw new Error('Dummy')
}
@berthel350
const getUserStub = () => {
return Promise
.resolve({friends: []})
}
const getUserStub = jest.fn(() => {
return Promise
.resolve({friends: []})
})
@berthel350
const getUserMock = jest.fn((userID) => {
if(userId === 'flo') {
return Promise
.resolve({friends: ['martinique']})
}
return Promise
.reject(new Error('Remi sans amis, sans famille'))
})
@berthel350
jest.spyOn(console, 'error');
expect(console.error).toHaveBeenCalled();
@berthel350
jest
.spyOn(console, 'error')
.mockImplementation(() => {});
const getUserStub = jest.fn(() => {
return Promise
.resolve({friends: []})
})
expect(getUserStub).toHaveBeenCalled();
@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;
}
}
@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);
})
})
})
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.
Confiné en mémoire (RAM)
Test déconfiné
Possibilité d'accéder aux fichiers, au réseau, ...
Interdit de bouchonner
Comme pour le vin, il est interdit d'utiliser des mocks, des spys, ...
ISO prod
Valide mon travail
Si ce test passe alors ma fonctionnalité est développée
Gerkin, Cucumber, ...
Valide mon travail
Si ce test passe alors ma fonctionnalité est développée
Gerkin, Cucumber, ...
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
Valide mon travail
@berthel350
Écrire le test
Le test échoue
Faire passer le test
Refactor
Le test passe encore
Coder test d'acceptance
@berthel350
Qui écrit les test ?
Product Owner => BDD
Les Devs => ATDD
Unit
Integration
E2E
Acceptance
Execution time
0
+∞
$
$$$
Unit
integration
E2E
acceptance
@berthel350
Unit
Integration
E2E
Execution time
0
+∞
$
$$$
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.
@berthel350
@berthel350
Choisissez le Framework Front et de test :)
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()
})
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();
})
Testez au moins un composant qui faire une requête HTTP vers la pokéAPI
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
Testez le reste de votre application en choisi la méthode que vous voulez utiliser
Puis...
et même...
Unit
Integration
E2E
Acceptance
Execution time
0
+∞
$
$$$
Compilation
Integration
E2E
Acceptance
Execution time
0
+∞
$
$$$
Static
Unit
Manuels
Dev2
Ordi
Dev1
Dev3
L'un code des tests pour piéger celui qui code.
PS: ceci est un dogme 🙃
florent@berthelot.io
berthelot.io
@FBerthelot sur GitHub
@Berthel350 sur GitHub
En max 3 commandes, je peux dev
Toutes ce qui est lancé sur la CI/CD peut être lancé sur mon poste