Maison > interface Web > js tutoriel > Comment écrire des tests unitaires et des Etests pour les applications NestJS

Comment écrire des tests unitaires et des Etests pour les applications NestJS

Mary-Kate Olsen
Libérer: 2025-01-13 06:22:43
original
348 Les gens l'ont consulté

Introduction

Récemment, j'ai écrit des tests unitaires et des tests E2E pour un projet NestJS. C'est la première fois que j'écris des tests pour un projet backend, et j'ai trouvé le processus différent de mon expérience avec les tests frontend, ce qui rend le démarrage difficile. Après avoir examiné quelques exemples, j'ai acquis une compréhension plus claire de la façon d'aborder les tests. Je prévois donc d'écrire un article pour enregistrer et partager mon apprentissage afin d'aider d'autres personnes susceptibles d'être confrontées à une confusion similaire.

De plus, j'ai mis en place un projet de démonstration avec les tests unitaires et E2E pertinents terminés, qui peuvent être intéressants. Le code a été téléchargé sur Github : https://github.com/woai3c/nestjs-demo.

Différence entre les tests unitaires et les tests E2E

Les tests unitaires et les tests E2E sont des méthodes de tests logiciels, mais ils ont des objectifs et des portées différents.

Les tests unitaires impliquent de vérifier et de vérifier la plus petite unité testable dans le logiciel. Une fonction ou une méthode, par exemple, peut être considérée comme une unité. Dans les tests unitaires, vous fournissez les sorties attendues pour diverses entrées d’une fonction et validez l’exactitude de son fonctionnement. L'objectif des tests unitaires est d'identifier rapidement les bogues au sein de la fonction, et ils sont faciles à écrire et à exécuter rapidement.

D'un autre côté, les tests E2E simulent souvent des scénarios d'utilisation réels pour tester l'ensemble de l'application. Par exemple, le frontend utilise généralement un navigateur ou un navigateur sans tête pour les tests, tandis que le backend le fait en simulant des appels API.

Au sein d'un projet NestJS, les tests unitaires peuvent évaluer un service spécifique ou une méthode d'un contrôleur, par exemple vérifier si la méthode de mise à jour dans le module Utilisateurs met correctement à jour un utilisateur. Un test E2E, cependant, peut examiner un parcours utilisateur complet, depuis la création d'un nouvel utilisateur jusqu'à la mise à jour de son mot de passe et éventuellement la suppression de l'utilisateur, ce qui implique plusieurs services et contrôleurs.

Écriture de tests unitaires

Écrire des tests unitaires pour une fonction ou une méthode utilitaire qui n’implique pas d’interfaces est relativement simple. Il vous suffit de considérer les différentes entrées et d'écrire le code de test correspondant. Cependant, la situation devient plus complexe dès lors que les interfaces entrent en jeu. Prenons le code comme exemple :

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le code ci-dessus est une méthode validateUser dans le fichier auth.service.ts, principalement utilisée pour vérifier si le nom d'utilisateur et le mot de passe saisis par l'utilisateur lors de la connexion sont corrects. Il contient la logique suivante :

  1. Vérifiez si l'utilisateur existe en fonction du nom d'utilisateur ; sinon, lancez une exception 401 (une exception 404 est également réalisable).
  2. Vérifiez si l'utilisateur est verrouillé ; si c'est le cas, lancez une exception 401 avec un message pertinent.
  3. Cryptez le mot de passe et comparez-le avec le mot de passe dans la base de données ; s'il est incorrect, lancez une exception 401 (trois tentatives de connexion consécutives infructueuses verrouillent le compte pendant 5 minutes).
  4. Si la connexion réussit, effacez le nombre de tentatives de connexion précédemment échouées (le cas échéant) et renvoyez l'identifiant et le nom d'utilisateur à l'étape suivante.

Comme on peut le voir, la méthode validateUser comprend quatre logiques de traitement, et nous devons écrire le code de test unitaire correspondant pour ces quatre points afin de garantir que l'ensemble de la fonction validateUser fonctionne correctement.

Le premier cas de test

Lorsque nous commençons à écrire des tests unitaires, nous rencontrons un problème : la méthode findOne doit interagir avec la base de données, et elle recherche les utilisateurs correspondants dans la base de données via le nom d'utilisateur. Cependant, si chaque test unitaire devait interagir avec la base de données, les tests deviendraient très fastidieux. Par conséquent, nous pouvons nous moquer des fausses données pour y parvenir.

Par exemple, supposons que nous ayons enregistré un utilisateur nommé woai3c. Ensuite, lors de la connexion, les données utilisateur peuvent être récupérées dans la méthode validateUser via constentity = wait this.usersService.findOne({ username });. Tant que cette ligne de code peut renvoyer les données souhaitées, il n'y a aucun problème, même sans interaction avec la base de données. Nous pouvons y parvenir grâce à des données fictives. Examinons maintenant le code de test pertinent pour la méthode validateUser :

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous obtenons les données utilisateur en appelant la méthode findOne de usersService, nous devons donc nous moquer de la méthode findOne de usersService dans le code de test :

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';
describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;
  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    // other tests...
  });
});
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous utilisons jest.fn() pour renvoyer une fonction pour remplacer le vrai usersService.findOne(). Si usersService.findOne() est appelé maintenant, il n'y aura aucune valeur de retour, donc le premier cas de test unitaire réussira :

beforeEach(async () => {
    usersService = {
      findOne: jest.fn(), // mock findOne method
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService, // real AuthService, because we are testing its methods
        {
          provide: UsersService, // use mock usersService instead of real usersService
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
Copier après la connexion
Copier après la connexion

Depuis findOne in constentity = wait this.usersService.findOne({ username }); de la méthode validateUser est une fausse fonction simulée sans valeur de retour, les 2e à 4e lignes de code de la méthode validateUser pourraient exécuter :

it('should throw an UnauthorizedException if user is not found', async () => {
  await expect(
    authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
  ).rejects.toThrow(UnauthorizedException);
});
Copier après la connexion

Lance une erreur 401, ce qui est comme prévu.

Le deuxième cas de test

La deuxième logique de la méthode validateUser est de déterminer si l'utilisateur est verrouillé, avec le code correspondant comme suit :

if (!entity) {
  throw new UnauthorizedException('User not found');
}
Copier après la connexion

Comme vous pouvez le voir, nous pouvons déterminer que le compte actuel est verrouillé s'il y a un temps de verrouillage lockUntil dans les données utilisateur et que l'heure de fin du verrouillage est supérieure à l'heure actuelle. Par conséquent, nous devons simuler les données d'un utilisateur avec le champ lockUntil :

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans le code de test ci-dessus, un objet verrouilléUser est d'abord défini, qui contient le champ lockUntil dont nous avons besoin. Ensuite, il est utilisé comme valeur de retour pour findOne, obtenu par usersService.findOne.mockResolvedValueOnce(lockedUser);. Ainsi, lorsque la méthode validateUser est exécutée, les données utilisateur qu'elle contient sont les données simulées, permettant ainsi au deuxième scénario de test de réussir.

Couverture des tests unitaires

La couverture des tests unitaires (Code Coverage) est une métrique utilisée pour décrire la quantité de code de l'application qui a été couverte ou testée par des tests unitaires. Il est généralement exprimé sous forme de pourcentage, indiquant dans quelle mesure tous les chemins de code possibles ont été couverts par les cas de test.

La couverture des tests unitaires comprend généralement les types suivants :

  • Couverture des lignes : combien de lignes de code sont couvertes par les tests.
  • Couverture des fonctions : combien de fonctions ou de méthodes sont couvertes par les tests.
  • Couverture des branches : combien de branches de code sont couvertes par les tests (par exemple, les instructions if/else).
  • Couverture des déclarations : combien d'instructions dans le code sont couvertes par les tests.

La couverture des tests unitaires est une mesure importante pour mesurer la qualité des tests unitaires, mais ce n'est pas la seule mesure. Un taux de couverture élevé peut aider à détecter les erreurs dans le code, mais il ne garantit pas la qualité du code. Un faible taux de couverture peut signifier qu'il existe du code non testé, potentiellement avec des erreurs non détectées.

L'image ci-dessous montre les résultats de la couverture des tests unitaires pour un projet de démonstration :

How to write unit tests and Etests for NestJS applications

Pour les fichiers comme les services et les contrôleurs, il est généralement préférable d'avoir une couverture de tests unitaires plus élevée, tandis que pour les fichiers comme les modules, il n'est pas nécessaire d'écrire des tests unitaires, ni qu'il n'est possible de les écrire, car cela n'a aucun sens. L'image ci-dessus représente les métriques globales pour l'ensemble de la couverture des tests unitaires. Si vous souhaitez afficher la couverture des tests pour une fonction spécifique, vous pouvez ouvrir le fichier cover/lcov-report/index.html dans le répertoire racine du projet. Par exemple, je souhaite voir la situation de test spécifique pour la méthode validateUser :

How to write unit tests and Etests for NestJS applications

Comme on peut le voir, la couverture des tests unitaires d'origine pour la méthode validateUser n'est pas de 100 %, et il reste encore deux lignes de code qui n'ont pas été exécutées. Cependant, cela n'a pas beaucoup d'importance, car cela n'affecte pas les quatre nœuds de traitement clés, et il ne faut pas rechercher une couverture de test élevée de manière unidimensionnelle.

Rédaction de tests E2E

Dans les tests unitaires, nous avons montré comment écrire des tests unitaires pour chaque fonctionnalité de la fonction validateUser(), en utilisant des données simulées pour garantir que chaque fonctionnalité pouvait être testée. Dans les tests e2E, nous devons simuler des scénarios d'utilisateurs réels, il est donc nécessaire de se connecter à une base de données pour les tests. Par conséquent, les méthodes du module auth.service.ts que nous allons tester interagissent toutes avec la base de données.

Le module d'authentification comprend principalement les fonctionnalités suivantes :

  • Inscription
  • Connexion
  • Actualisation du jeton
  • Lecture des informations utilisateur
  • Changer le mot de passe
  • Supprimer des utilisateurs

Les tests E2E doivent tester ces six fonctionnalités une par une, en commençant par l'enregistrement et en terminant par la suppression des utilisateurs. Pendant les tests, nous pouvons créer un utilisateur test dédié pour effectuer les tests, puis supprimer cet utilisateur test une fois terminé, afin de ne laisser aucune information inutile dans la base de données des tests.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La fonction hook beforeAll s'exécute avant le début de tous les tests, nous pouvons donc enregistrer un compte de test TEST_USER_NAME ici. La fonction de hook afterAll s'exécute après la fin de tous les tests, il convient donc de supprimer le compte de test TEST_USER_NAME ici, et elle teste également facilement les fonctions d'enregistrement et de suppression.

Dans les tests unitaires de la section précédente, nous avons écrit des tests unitaires pertinents autour de la méthode validateUser. En fait, cette méthode est exécutée lors de la connexion pour valider si le compte et le mot de passe de l'utilisateur sont corrects. Par conséquent, ce test e2E utilisera également le processus de connexion pour démontrer comment composer des cas de test e2E.

L'ensemble du processus de test de connexion comprend cinq petits tests :

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';
describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;
  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    // other tests...
  });
});
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ces cinq tests sont les suivants :

  1. Connexion réussie, renvoie 200
  2. Si l'utilisateur n'existe pas, lancez une exception 401
  3. Si le mot de passe ou le nom d'utilisateur n'est pas fourni, lancez une exception 400
  4. Connectez-vous avec un mauvais mot de passe, lancez une exception 401
  5. Si le compte est verrouillé, lancez une exception 401

Commençons maintenant à écrire les tests e2E :

beforeEach(async () => {
    usersService = {
      findOne: jest.fn(), // mock findOne method
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService, // real AuthService, because we are testing its methods
        {
          provide: UsersService, // use mock usersService instead of real usersService
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
Copier après la connexion
Copier après la connexion

L'écriture du code de test e2E est relativement simple : il vous suffit d'appeler l'interface puis de vérifier le résultat. Par exemple, pour réussir le test de connexion, il suffit de vérifier que le résultat renvoyé est 200.

Les quatre premiers tests sont assez simples. Examinons maintenant un test e2E légèrement plus compliqué, qui consiste à vérifier si un compte est verrouillé.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Lorsqu'un utilisateur ne parvient pas à se connecter trois fois de suite, le compte sera verrouillé. Par conséquent, dans ce test, nous ne pouvons pas utiliser le compte de test TEST_USER_NAME, car si le test réussit, ce compte sera verrouillé et ne pourra pas continuer les tests suivants. Nous devons enregistrer un autre nouvel utilisateur TEST_USER_NAME2 spécifiquement pour tester le verrouillage du compte et supprimer cet utilisateur une fois le test réussi. Ainsi, comme vous pouvez le voir, le code de ce test e2E est assez substantiel, nécessitant beaucoup de travail d'installation et de démontage, mais le code de test réel ne contient que ces quelques lignes :

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';
describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;
  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    // other tests...
  });
});
Copier après la connexion
Copier après la connexion
Copier après la connexion

L'écriture du code de test e2E est relativement simple. Vous n’avez pas besoin de prendre en compte les données fictives ou la couverture des tests. Il suffit que l’ensemble du processus système se déroule comme prévu.

S'il faut écrire des tests

Si possible, je recommande généralement d'écrire des tests. Cela peut améliorer la robustesse, la maintenabilité et l'efficacité du développement du système.

Améliorer la robustesse du système

Lors de l'écriture du code, nous nous concentrons généralement sur le déroulement du programme avec des entrées normales pour garantir le bon fonctionnement des fonctionnalités de base. Cependant, nous pouvons souvent négliger certains cas extrêmes, tels que des entrées anormales. L'écriture de tests change cela ; cela vous oblige à réfléchir à la manière de gérer ces cas et de réagir de manière appropriée, évitant ainsi les plantages. On peut dire que l'écriture de tests améliore indirectement la robustesse du système.

Améliorer la maintenabilité

Reprendre un nouveau projet qui comprend des tests complets peut être très agréable. Ils font office de guide et vous aident à comprendre rapidement les différentes fonctionnalités. En regardant simplement le code de test, vous pouvez facilement comprendre le comportement attendu et les conditions aux limites de chaque fonction sans avoir à parcourir chaque ligne du code de la fonction.

Améliorer l’efficacité du développement

Imaginez, un projet qui n'a pas été mis à jour depuis un certain temps reçoit soudainement de nouvelles exigences. Après avoir apporté des modifications, vous pourriez craindre d’introduire des bugs. Sans tests, vous auriez besoin de tester à nouveau manuellement l’ensemble du projet, ce qui vous ferait perdre du temps et serait inefficace. Avec des tests complets, une seule commande peut vous dire si les modifications du code ont impacté les fonctionnalités existantes. Même s'il y a des erreurs, elles peuvent être rapidement localisées et corrigées.

Quand ne pas écrire de tests ?

Pour les projets à court terme et les projets avec itérations d'exigences très rapides, il n'est pas recommandé d'écrire des tests. Par exemple, certains projets destinés à des événements qui seront inutiles une fois l’événement terminé n’ont pas besoin de tests. De plus, pour les projets qui subissent des itérations d'exigences très rapides, j'ai dit que l'écriture de tests pourrait améliorer l'efficacité du développement, mais cela est basé sur le principe que les itérations de fonctions sont lentes. Si la fonction que vous venez de terminer change dans un jour ou deux, le code de test correspondant doit être réécrit. Il est donc préférable de ne pas écrire de tests du tout et de s’en remettre à l’équipe de test, car l’écriture de tests prend beaucoup de temps et n’en vaut pas la peine.

Conclusion

Après avoir expliqué en détail comment écrire des tests unitaires et des tests e2E pour les projets NestJS, je souhaite quand même réitérer l'importance des tests. Cela peut améliorer la robustesse, la maintenabilité et l’efficacité du développement du système. Si vous n'avez pas la possibilité d'écrire des tests, je vous suggère de démarrer vous-même un projet pratique ou de participer à certains projets open source et d'y contribuer du code. Les projets open source ont généralement des exigences de code plus strictes. La contribution du code peut vous obliger à écrire de nouveaux cas de test ou à modifier ceux existants.

Documents de référence

  • NestJS : un framework pour créer des applications côté serveur Node.js efficaces et évolutives.
  • MongoDB : Une base de données NoSQL utilisée pour le stockage des données.
  • Jest : un framework de test pour JavaScript et TypeScript.
  • Supertest : Une bibliothèque pour tester les serveurs HTTP.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal