0%
Guide complet des tests en JavaScript

Guide complet des tests en JavaScript

Stratégies et outils pour tester efficacement vos applications JavaScript et garantir leur qualité.

I

InSkillCoach

· min

Guide complet des tests en JavaScript

Les tests sont un aspect fondamental du développement logiciel professionnel. Ils garantissent que votre code fonctionne comme prévu, facilitent la maintenance et permettent de détecter les régressions. Cet article explore les différentes approches et outils pour tester efficacement vos applications JavaScript.

Pourquoi tester votre code ?

  • Confiance : Assurance que votre code fonctionne correctement
  • Documentation : Les tests servent de documentation vivante
  • Refactoring sécurisé : Modification du code sans craindre de casser des fonctionnalités
  • Qualité : Réduction des bugs et amélioration de la conception
  • Collaboration : Facilitation du travail en équipe
  • Déploiement continu : Automatisation des vérifications avant déploiement

Types de tests

Tests unitaires

Les tests unitaires vérifient le bon fonctionnement des plus petites unités de code (fonctions, méthodes, classes) de manière isolée.

// Fonction à tester
function somme(a, b) {
  return a + b;
}

// Test unitaire avec Jest
test('additionne correctement deux nombres', () => {
  expect(somme(2, 3)).toBe(5);
  expect(somme(-1, 1)).toBe(0);
  expect(somme(0, 0)).toBe(0);
});

Tests d’intégration

Les tests d’intégration vérifient que différentes parties du système fonctionnent correctement ensemble.

// Test d'intégration avec Jest
describe('Service utilisateur', () => {
  test('crée un utilisateur et le récupère de la base de données', async () => {
    // Créer un utilisateur
    const userId = await userService.createUser({
      name: 'Alice',
      email: 'alice@example.com'
    });
    
    // Récupérer l'utilisateur
    const user = await userService.getUserById(userId);
    
    // Vérifier que l'utilisateur est correctement récupéré
    expect(user).toEqual({
      id: userId,
      name: 'Alice',
      email: 'alice@example.com'
    });
  });
});

Tests fonctionnels / End-to-End (E2E)

Les tests E2E vérifient le comportement de l’application du point de vue de l’utilisateur final.

// Test E2E avec Cypress
describe('Formulaire de connexion', () => {
  it('permet à un utilisateur de se connecter', () => {
    cy.visit('/login');
    
    cy.get('input[name="email"]').type('utilisateur@exemple.fr');
    cy.get('input[name="password"]').type('motdepasse123');
    cy.get('button[type="submit"]').click();
    
    // Vérifier que l'utilisateur est redirigé vers le tableau de bord
    cy.url().should('include', '/dashboard');
    cy.contains('Bienvenue, Utilisateur').should('be.visible');
  });
});

Tests de composants

Pour les applications basées sur des composants (React, Vue, Angular), les tests de composants vérifient le rendu et le comportement des composants UI.

// Test de composant React avec React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import Compteur from './Compteur';

test('incrémente le compteur lorsque le bouton est cliqué', () => {
  render(<Compteur />);
  
  // Vérifier l'état initial
  expect(screen.getByText('Compteur: 0')).toBeInTheDocument();
  
  // Cliquer sur le bouton
  fireEvent.click(screen.getByText('Incrémenter'));
  
  // Vérifier que le compteur a été incrémenté
  expect(screen.getByText('Compteur: 1')).toBeInTheDocument();
});

Tests de performance

Les tests de performance vérifient que votre application répond aux exigences de vitesse et d’efficacité.

// Test de performance avec Lighthouse CI
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
        'interactive': ['error', { maxNumericValue: 3000 }],
        'max-potential-fid': ['error', { maxNumericValue: 100 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Tests de sécurité

Les tests de sécurité identifient les vulnérabilités potentielles dans votre application.

// Exemple avec OWASP ZAP (via CLI)
// zap-cli quick-scan --self-contained --start-options "-config api.disablekey=true" https://example.com

Frameworks et outils de test populaires

Jest

Jest est un framework de test JavaScript complet avec une configuration minimale.

// Installation
// npm install --save-dev jest

// Configuration minimale (package.json)
{
  "scripts": {
    "test": "jest"
  }
}

// Exemple de test
const { somme } = require('./math');

describe('Fonctions mathématiques', () => {
  test('somme additionne deux nombres', () => {
    expect(somme(1, 2)).toBe(3);
  });
  
  test('somme gère les nombres négatifs', () => {
    expect(somme(-1, -2)).toBe(-3);
  });
  
  test('somme avec zéro retourne l\'autre nombre', () => {
    expect(somme(0, 5)).toBe(5);
  });
});

Mocha + Chai

Mocha est un framework de test flexible, souvent utilisé avec Chai pour les assertions.

// Installation
// npm install --save-dev mocha chai

// Exemple de test
const { expect } = require('chai');
const { somme } = require('./math');

describe('Fonctions mathématiques', () => {
  it('somme additionne deux nombres', () => {
    expect(somme(1, 2)).to.equal(3);
  });
  
  it('somme gère les nombres négatifs', () => {
    expect(somme(-1, -2)).to.equal(-3);
  });
});

Cypress

Cypress est un outil de test E2E moderne qui s’exécute dans le navigateur.

// Installation
// npm install --save-dev cypress

// Exemple de test
describe('Page d\'accueil', () => {
  beforeEach(() => {
    cy.visit('/');
  });
  
  it('affiche le titre correct', () => {
    cy.get('h1').should('contain', 'Bienvenue sur notre site');
  });
  
  it('navigue vers la page À propos', () => {
    cy.get('nav').contains('À propos').click();
    cy.url().should('include', '/about');
    cy.get('h1').should('contain', 'À propos de nous');
  });
});

Playwright

Playwright est un outil de test E2E qui prend en charge plusieurs navigateurs.

// Installation
// npm install --save-dev @playwright/test

// Exemple de test
const { test, expect } = require('@playwright/test');

test('page d\'accueil a le bon titre', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Vérifier le titre
  const title = await page.title();
  expect(title).toBe('Example Domain');
  
  // Vérifier le contenu
  const heading = await page.textContent('h1');
  expect(heading).toBe('Example Domain');
});

Testing Library

Testing Library est une famille de bibliothèques pour tester les interfaces utilisateur de manière centrée sur l’utilisateur.

// Installation pour React
// npm install --save-dev @testing-library/react @testing-library/jest-dom

// Exemple de test
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Formulaire from './Formulaire';

test('soumet le formulaire avec les valeurs correctes', () => {
  const handleSubmit = jest.fn();
  render(<Formulaire onSubmit={handleSubmit} />);
  
  // Remplir le formulaire
  fireEvent.change(screen.getByLabelText(/nom/i), {
    target: { value: 'Alice' },
  });
  
  fireEvent.change(screen.getByLabelText(/email/i), {
    target: { value: 'alice@example.com' },
  });
  
  // Soumettre le formulaire
  fireEvent.click(screen.getByRole('button', { name: /soumettre/i }));
  
  // Vérifier que la fonction onSubmit a été appelée avec les bonnes valeurs
  expect(handleSubmit).toHaveBeenCalledWith({
    nom: 'Alice',
    email: 'alice@example.com',
  });
});

Storybook

Storybook permet de développer et tester des composants UI de manière isolée.

// Installation
// npx sb init

// Exemple de story
// Button.stories.js
import React from 'react';
import { Button } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

Approches de test

Test-Driven Development (TDD)

Le TDD suit un cycle en trois étapes :

  1. Red : Écrire un test qui échoue
  2. Green : Écrire le code minimal pour faire passer le test
  3. Refactor : Améliorer le code sans changer son comportement
// Exemple de TDD
// 1. Red: Écrire un test qui échoue
test('convertit les degrés Celsius en Fahrenheit', () => {
  expect(celsiusToFahrenheit(0)).toBe(32);
  expect(celsiusToFahrenheit(100)).toBe(212);
});

// 2. Green: Écrire le code minimal pour faire passer le test
function celsiusToFahrenheit(celsius) {
  return celsius * 9/5 + 32;
}

// 3. Refactor: Améliorer le code (si nécessaire)
function celsiusToFahrenheit(celsius) {
  const factor = 9/5;
  const freezingPointDiff = 32;
  return celsius * factor + freezingPointDiff;
}

Behavior-Driven Development (BDD)

Le BDD se concentre sur le comportement attendu du système du point de vue de l’utilisateur.

// Exemple avec Cucumber.js
// Feature: Connexion utilisateur
//   En tant qu'utilisateur enregistré
//   Je veux pouvoir me connecter
//   Afin d'accéder à mon compte

// Scénario: Connexion réussie
//   Étant donné que je suis sur la page de connexion
//   Quand je saisis mon email "utilisateur@exemple.fr"
//   Et je saisis mon mot de passe "motdepasse123"
//   Et je clique sur le bouton "Se connecter"
//   Alors je devrais être redirigé vers la page "Tableau de bord"
//   Et je devrais voir "Bienvenue, Utilisateur"

// Implémentation des étapes
const { Given, When, Then } = require('@cucumber/cucumber');

Given('je suis sur la page de connexion', async function() {
  await this.navigateTo('/login');
});

When('je saisis mon email {string}', async function(email) {
  await this.fillField('email', email);
});

// ... autres étapes

Mocks, Stubs et Spies

Mocks

Les mocks remplacent des dépendances réelles par des versions simulées.

// Exemple avec Jest
test('envoie un email de bienvenue lors de l\'inscription', () => {
  // Mock du service d'email
  const emailService = {
    sendWelcomeEmail: jest.fn()
  };
  
  // Système sous test
  const userService = new UserService(emailService);
  
  // Action
  userService.registerUser('alice@example.com', 'password123');
  
  // Assertion
  expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('alice@example.com');
});

Stubs

Les stubs fournissent des réponses prédéfinies aux appels de fonction.

// Exemple avec Sinon.js
const sinon = require('sinon');
const database = require('./database');

test('récupère les données utilisateur', async () => {
  // Créer un stub pour la méthode query
  const queryStub = sinon.stub(database, 'query');
  
  // Configurer le stub pour retourner des données spécifiques
  queryStub.withArgs('SELECT * FROM users WHERE id = ?', [42])
    .resolves([{ id: 42, name: 'Alice' }]);
  
  // Système sous test
  const userService = new UserService(database);
  
  // Action
  const user = await userService.getUserById(42);
  
  // Assertion
  expect(user).toEqual({ id: 42, name: 'Alice' });
  
  // Restaurer le stub
  queryStub.restore();
});

Spies

Les spies observent les appels de fonction sans modifier leur comportement.

// Exemple avec Jest
test('enregistre les erreurs dans la console', () => {
  // Créer un spy sur console.error
  const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
  
  // Action qui devrait générer une erreur
  const calculator = new Calculator();
  calculator.divide(10, 0);
  
  // Assertion
  expect(errorSpy).toHaveBeenCalledWith('Division par zéro');
  
  // Restaurer le spy
  errorSpy.mockRestore();
});

Tests asynchrones

Promises

// Test avec Jest
test('récupère les données utilisateur', () => {
  return userService.getUserById(42)
    .then(user => {
      expect(user.name).toBe('Alice');
    });
});

// Ou avec async/await
test('récupère les données utilisateur', async () => {
  const user = await userService.getUserById(42);
  expect(user.name).toBe('Alice');
});

Callbacks

// Test avec Jest
test('lit un fichier', done => {
  fs.readFile('data.txt', 'utf8', (err, data) => {
    if (err) {
      done(err);
      return;
    }
    
    try {
      expect(data).toContain('Hello, world');
      done();
    } catch (error) {
      done(error);
    }
  });
});

Couverture de code

La couverture de code mesure quelle partie de votre code est exécutée pendant les tests.

// Configuration Jest pour la couverture
// package.json
{
  "jest": {
    "collectCoverage": true,
    "collectCoverageFrom": [
      "src/**/*.{js,jsx}",
      "!**/node_modules/**",
      "!**/vendor/**"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Intégration continue (CI)

L’intégration continue exécute automatiquement vos tests à chaque modification du code.

# Exemple avec GitHub Actions
# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Run tests
      run: npm test
      
    - name: Upload coverage
      uses: codecov/codecov-action@v2

Bonnes pratiques

  1. Testez d’abord les fonctionnalités critiques : Concentrez-vous sur le code le plus important
  2. Gardez les tests simples et lisibles : Un test doit être facile à comprendre
  3. Un test, une assertion : Chaque test devrait vérifier une seule chose
  4. Isolez vos tests : Les tests ne doivent pas dépendre les uns des autres
  5. Utilisez des données de test réalistes : Évitez les données trop simplistes
  6. Évitez les tests fragiles : Les tests ne doivent pas casser pour des raisons superficielles
  7. Exécutez les tests régulièrement : Idéalement à chaque modification du code
  8. Maintenez vos tests : Les tests sont du code et nécessitent de la maintenance

Pièges courants à éviter

  1. Tester l’implémentation plutôt que le comportement : Concentrez-vous sur ce que fait le code, pas comment il le fait
  2. Ignorer les cas limites : Testez les valeurs extrêmes et les cas d’erreur
  3. Tester uniquement le chemin heureux : N’oubliez pas de tester les cas d’échec
  4. Tests trop couplés au code : Les tests trop spécifiques cassent facilement lors des refactorings
  5. Négliger les tests de régression : Ajoutez des tests pour les bugs corrigés
  6. Tests lents : Des tests lents découragent leur exécution fréquente

Conclusion

Les tests sont un investissement qui paie des dividendes tout au long de la vie d’un projet. Ils améliorent la qualité du code, facilitent la maintenance et permettent des déploiements plus fréquents et plus fiables.

Il n’existe pas d’approche universelle pour les tests. La stratégie optimale dépend de la nature de votre projet, de votre équipe et de vos contraintes. L’important est de commencer quelque part et d’améliorer progressivement votre couverture de test.

En adoptant une culture de test dans votre équipe et en suivant les bonnes pratiques présentées dans cet article, vous pouvez considérablement améliorer la qualité et la maintenabilité de vos applications JavaScript.

InSkillCoach

À propos de InSkillCoach

Expert en formation et technologies

Coach spécialisé dans les technologies avancées et l'IA, porté par GNeurone Inc.

Certifications:

  • AWS Certified Solutions Architect – Professional
  • Certifications Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
995
322

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !