Imaginez que vous avez développé une fonction complexe pour calculer les remises dans une boutique en ligne. Le code fonctionne très bien, mais après un mois, un collègue apporte de petites modifications à une autre partie de l'application, et soudain, vos calculs commencent à donner des résultats incorrects. Comment détecter un tel problème à temps ? La réponse est simple : les tests unitaires.
Que sont les tests unitaires
Les tests unitaires sont des vérifications automatisées de petites sections de code, généralement des fonctions ou des méthodes individuelles. Ils fonctionnent comme des points de contrôle qui vérifient constamment que votre code se comporte exactement comme prévu.
L'idée de base est simple : vous écrivez un code qui appelle votre fonction avec des données d'entrée connues et vérifie que le résultat correspond à ce qui est attendu. Si le résultat est correct, le test est réussi, sinon, le test échoue et signale une erreur.
Pourquoi avons-nous besoin de tests unitaires
Les développeurs débutants se demandent souvent : pourquoi perdre du temps à écrire des tests, si vous pouvez simplement vérifier le code manuellement ? Il y a plusieurs raisons.
La première raison est la confiance dans le remaniement. Lorsque vous avez une couverture de test, vous pouvez améliorer le code en toute sécurité, sachant que si quelque chose se casse, les tests le signaleront immédiatement. C'est comme un filet de sécurité pour un acrobate.
La deuxième raison est la documentation. Des tests bien écrits montrent comment une fonction doit être utilisée, quelles données d'entrée elle accepte et ce qu'elle renvoie. Il s'agit d'une documentation vivante qui est toujours à jour.
La troisième raison est le gain de temps à long terme. Oui, la rédaction de tests prend du temps maintenant, mais cela permettra d'économiser des heures de débogage à l'avenir. Les tests automatisés s'exécutent en quelques secondes et vérifient toutes les fonctionnalités, alors que les tests manuels peuvent prendre des heures.

Un exemple simple
Prenons un exemple pratique en JavaScript. Supposons que nous ayons une fonction pour valider les adresses e-mail :
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}Maintenant, écrivons un test unitaire pour elle en utilisant le framework populaire Jest :
describe('isValidEmail', () => {
test('must return true for a valid email', () => {
expect(isValidEmail('user@example.com')).toBe(true);
});
test('must return false for email without @', () => {
expect(isValidEmail('userexample.com')).toBe(false);
});
test('must return false for email without domain', () => {
expect(isValidEmail('user@')).toBe(false);
});
test('must return false for an empty string', () => {
expect(isValidEmail('')).toBe(false);
});
});Chaque test vérifie un scénario spécifique. Si quelqu'un modifie l'expression régulière et rompt accidentellement la validation, les tests le détecteront immédiatement.
Principes de rédaction de bons tests
Un bon test unitaire doit être indépendant. Cela signifie qu'il ne doit pas dépendre des résultats d'autres tests ou de l'ordre dans lequel ils sont effectués. Chaque test crée ses propres données et ne vérifie qu'une seule chose spécifique.
Les tests doivent être rapides. Si tous les tests prennent des minutes à s'exécuter, les développeurs les exécuteront moins souvent. Les tests unitaires doivent être effectués en quelques millisecondes afin qu'ils puissent être exécutés après chaque modification du code.
Les tests doivent être clairs. Lorsque le test échoue, vous devez immédiatement comprendre ce qui s'est passé. Utilisez des noms de test descriptifs et des messages d'erreur clairs.
Motif AAA
Une approche populaire de la structuration des tests est le modèle AAA : Arrange, Act, Assert (Préparation, Action, Vérification).
test('must correctly calculate the 10% discount', () => {
// Arrange — préparation des données
const price = 1000;
const discount = 10;
// Act — exécution d'une action
const result = calculateDiscount(price, discount);
// Assert — vérification du résultat
expect(result).toBe(900);
});Une telle structure rend les tests lisibles et compréhensibles même pour ceux qui voient le code pour la première fois.
Test des cas limites
L'une des tâches les plus importantes des tests unitaires est la vérification des cas limites. Ce sont des situations qui sont à la limite des valeurs admissibles : tableaux vides, valeurs nulles, nombres maximum, caractères spéciaux.
describe('calculateAge', () => {
test('must return 0 for a newborn', () => {
const today = new Date();
expect(calculateAge(today)).toBe(0);
});
test('must process a leap year', () => {
const birthDate = new Date('2000-02-29');
expect(calculateAge(birthDate)).toBeGreaterThan(0);
});
test('should throw an error for a date in the future', () => {
const futureDate = new Date('2030-01-01');
expect(() => calculateAge(futureDate)).toThrow();
});
});Ce sont les cas limites qui deviennent le plus souvent une source de bogues en production.

Mocks et stubs
Souvent, la fonction dépend de services externes : bases de données, API, système de fichiers. Pour les tests unitaires, nous ne voulons pas utiliser de vraies ressources externes — c'est lent et peu fiable. Au lieu de cela, des simulations et des stubs sont utilisés.
// Fonction que nous testons
async function getUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// Test avec MOC
test('must receive user data', async () => {
// Créer un mock pour fetch
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ id: 1, name: 'Alexey' })
})
);
const userData = await getUserData(1);
expect(userData.name).toBe('Alexey');
expect(fetch).toHaveBeenCalledWith('/api/users/1');
});Les mocks vous permettent d'isoler le code testé et de vérifier que la fonction interagit correctement avec les dépendances externes.
Couverture du code par des tests
La couverture de code indique le pourcentage de votre code exécuté pendant les tests. La plupart des outils de test peuvent générer des rapports de couverture.
Cependant, il est important de comprendre qu'une couverture à 100 % ne garantit pas l'absence de bogues. La couverture indique que le code a été exécuté, mais ne garantit pas qu'il a été testé pour tous les scénarios possibles. Visez une couverture raisonnable des parties critiques du code.
Test dans différentes langues
Le concept de tests unitaires est universel, mais les outils varient selon le langage de programmation.
Les frameworks pytest et unittest sont populaires en Python. Ils fournissent une syntaxe simple pour l'écriture de tests et de nombreux utilitaires de test intégrés.
JavaScript et TypeScript utilisent Jest, Mocha, Jasmine. Jest est particulièrement populaire en raison de son support intégré des mocs et de son API pratique.
Dans les applications Vue.js, Vue Test Utils est souvent utilisé en conjonction avec Jest, ce qui vous permet de tester les composants de manière isolée, de vérifier leur rendu et leur interaction avec l'utilisateur.
Java utilise traditionnellement JUnit, et PHP utilise PHPUnit. Chacun de ces outils est adapté aux spécificités de sa langue, mais les principes de base restent les mêmes.
TDD : développement par tests
Certaines équipes pratiquent le TDD (Test-Driven Development), une approche dans laquelle les tests sont écrits avant le code principal. Le processus est le suivant : d'abord, vous écrivez un test défaillant décrivant le comportement souhaité, puis vous écrivez le code minimum pour que le test réussisse, et enfin, vous refactorisez le code, en gardant les tests verts.
Cette approche permet de mieux réfléchir à l'architecture et de s'assurer que tout le code est couvert par des tests. Cependant, TDD exige de la discipline et n'est pas toujours adapté aux projets expérimentaux.
Quand les tests unitaires ne sont pas nécessaires
Honnêtement, tous les codes n'ont pas besoin de tests unitaires. Les getters et setters simples, les fonctions de formatage triviales, les composants d'interface utilisateur sans logique — tout cela peut se faire sans tests ou être couvert par des tests d'intégration.
Concentrez-vous sur les tests de logique métier, d'algorithmes complexes, de fonctions de logique conditionnelle et de code essentiel au fonctionnement de l'application. C'est là que les tests seront les plus utiles.
Vous souhaitez approfondir vos connaissances sur les tests et autres pratiques de développement professionnel ?
Annexe Code propose des cours interactifs sur Python, JavaScript et de nombreuses autres technologies. La formation est construite du plus simple au plus complexe avec des exercices pratiques et des exemples réels.
Rejoignez notre Chaîne Telegram, où vous trouverez une communauté active de développeurs prêts à vous aider pour toutes vos questions, à partager leur expérience et à vous soutenir dans votre apprentissage de la programmation !
