Einführung
Vor kurzem habe ich Unit-Tests und E2E-Tests für ein NestJS-Projekt geschrieben. Dies ist das erste Mal, dass ich Tests für ein Backend-Projekt schreibe, und ich fand, dass sich der Prozess von meinen Erfahrungen mit Frontend-Tests unterscheidet, was den Anfang schwierig macht. Nachdem ich mir einige Beispiele angeschaut habe, habe ich ein klareres Verständnis dafür gewonnen, wie man beim Testen vorgeht. Deshalb habe ich vor, einen Artikel zu schreiben, um meine Erkenntnisse aufzuzeichnen und weiterzugeben, um anderen zu helfen, die möglicherweise mit ähnlichen Verwirrungen konfrontiert sind.
Darüber hinaus habe ich ein Demoprojekt mit der entsprechenden Einheit und abgeschlossenen E2E-Tests zusammengestellt, das von Interesse sein könnte. Der Code wurde auf Github hochgeladen: https://github.com/woai3c/nestjs-demo.
Unterschied zwischen Unit-Tests und E2E-Tests
Unit-Tests und E2E-Tests sind Methoden des Softwaretests, haben jedoch unterschiedliche Ziele und Anwendungsbereiche.
Bei Unit-Tests geht es um die Prüfung und Verifizierung der kleinsten testbaren Einheit innerhalb der Software. Als Einheit kann beispielsweise eine Funktion oder eine Methode betrachtet werden. Beim Unit-Test stellen Sie erwartete Ausgaben für verschiedene Eingaben einer Funktion bereit und validieren die Richtigkeit ihrer Funktionsweise. Das Ziel von Unit-Tests besteht darin, Fehler innerhalb der Funktion schnell zu identifizieren, und sie sind einfach zu schreiben und schnell auszuführen.
Andererseits simulieren E2E-Tests häufig reale Benutzerszenarien, um die gesamte Anwendung zu testen. Beispielsweise verwendet das Frontend typischerweise einen Browser oder einen Headless-Browser zum Testen, während das Backend dies durch die Simulation von API-Aufrufen tut.
Innerhalb eines NestJS-Projekts können Komponententests einen bestimmten Dienst oder eine Methode eines Controllers bewerten, z. B. die Überprüfung, ob die Aktualisierungsmethode im Benutzermodul einen Benutzer korrekt aktualisiert. Ein E2E-Test kann jedoch eine vollständige Benutzerreise untersuchen, von der Erstellung eines neuen Benutzers über die Aktualisierung seines Passworts bis hin zum schließlichen Löschen des Benutzers, was mehrere Dienste und Controller umfasst.
Unit-Tests schreiben
Das Schreiben von Unit-Tests für eine Dienstprogrammfunktion oder -methode, die keine Schnittstellen beinhaltet, ist relativ einfach. Sie müssen lediglich die verschiedenen Eingaben berücksichtigen und den entsprechenden Testcode schreiben. Allerdings wird die Situation komplexer, sobald Schnittstellen ins Spiel kommen. Nehmen wir als Beispiel Code:
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; } </useraccountdto>
Der obige Code ist eine Methode „validateUser“ in der Datei auth.service.ts, die hauptsächlich dazu dient, zu überprüfen, ob der Benutzername und das Passwort, die der Benutzer bei der Anmeldung eingegeben hat, korrekt sind. Es enthält die folgende Logik:
- Überprüfen Sie anhand des Benutzernamens, ob der Benutzer existiert. Wenn nicht, lösen Sie eine 401-Ausnahme aus (eine 404-Ausnahme ist ebenfalls möglich).
- Überprüfen Sie, ob der Benutzer gesperrt ist. Wenn ja, lösen Sie eine 401-Ausnahme mit einer entsprechenden Meldung aus.
- Verschlüsseln Sie das Passwort und vergleichen Sie es mit dem Passwort in der Datenbank; Wenn es falsch ist, lösen Sie eine 401-Ausnahme aus (drei aufeinanderfolgende fehlgeschlagene Anmeldeversuche sperren das Konto für 5 Minuten).
- Wenn die Anmeldung erfolgreich ist, löschen Sie alle zuvor fehlgeschlagenen Anmeldeversuche (falls zutreffend) und geben Sie die Benutzer-ID und den Benutzernamen an den nächsten Schritt zurück.
Wie zu sehen ist, umfasst die Methode „validateUser“ vier Verarbeitungslogiken, und wir müssen entsprechenden Komponententestcode für diese vier Punkte schreiben, um sicherzustellen, dass die gesamte Funktion „validateUser“ korrekt funktioniert.
Der erste Testfall
Wenn wir mit dem Schreiben von Unit-Tests beginnen, stoßen wir auf ein Problem: Die findOne-Methode muss mit der Datenbank interagieren und sucht über den Benutzernamen nach entsprechenden Benutzern in der Datenbank. Wenn jedoch jeder Unit-Test mit der Datenbank interagieren müsste, wäre das Testen sehr umständlich. Daher können wir gefälschte Daten vortäuschen, um dies zu erreichen.
Angenommen, wir haben einen Benutzer namens woai3c registriert. Anschließend können die Benutzerdaten während der Anmeldung in der Methode „validateUser“ über const entity = waiting this.usersService.findOne({ username }); abgerufen werden. Solange diese Codezeile die gewünschten Daten zurückgeben kann, gibt es auch ohne Datenbankinteraktion kein Problem. Wir können dies durch Scheindaten erreichen. Schauen wir uns nun den relevanten Testcode für die Methode „validateUser“ an:
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; } </useraccountdto>
Wir erhalten die Benutzerdaten, indem wir die Methode „findOne“ von „usersService“ aufrufen, daher müssen wir die Methode „findOne“ von „usersService“ im Testcode nachahmen:
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 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... }); }); </authservice></record>
Wir verwenden jest.fn(), um eine Funktion zurückzugeben, die den echten usersService.findOne() ersetzt. Wenn jetzt „usersService.findOne()“ aufgerufen wird, gibt es keinen Rückgabewert, sodass der erste Unit-Testfall bestanden wird:
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); }); </authservice>
Da findOne in const entity = waiting this.usersService.findOne({ username }); Da es sich bei der Methode „validateUser“ um eine nachgetäuschte Fake-Funktion ohne Rückgabewert handelt, könnte die zweite bis vierte Codezeile in der Methode „validateUser“ Folgendes ausführen:
it('should throw an UnauthorizedException if user is not found', async () => { await expect( authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD), ).rejects.toThrow(UnauthorizedException); });
Wird einen 401-Fehler auslösen, was wie erwartet ist.
Der zweite Testfall
Die zweite Logik in der Methode „validateUser“ besteht darin, mit dem entsprechenden Code wie folgt zu ermitteln, ob der Benutzer gesperrt ist:
if (!entity) { throw new UnauthorizedException('User not found'); }
Wie Sie sehen, können wir feststellen, dass das aktuelle Konto gesperrt ist, wenn in den Benutzerdaten eine Sperrzeit lockUntil vorliegt und die Endzeit der Sperre größer als die aktuelle Zeit ist. Daher müssen wir Benutzerdaten mit dem lockUntil-Feld verspotten:
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; } </useraccountdto>
Im obigen Testcode wird zunächst ein Objekt „lockedUser“ definiert, das das von uns benötigte Feld „lockUntil“ enthält. Anschließend wird er als Rückgabewert für findOne verwendet, der durch „usersService.findOne.mockResolvedValueOnce(lockedUser);“ erreicht wird. Wenn also die Methode „validateUser“ ausgeführt wird, sind die darin enthaltenen Benutzerdaten die simulierten Daten, wodurch der zweite Testfall erfolgreich bestanden werden kann.
Unit-Test-Abdeckung
Die Unit-Test-Abdeckung (Code Coverage) ist eine Metrik, die beschreibt, wie viel des Anwendungscodes durch Unit-Tests abgedeckt oder getestet wurde. Er wird normalerweise als Prozentsatz ausgedrückt und gibt an, wie viele aller möglichen Codepfade von Testfällen abgedeckt wurden.
Die Unit-Test-Abdeckung umfasst normalerweise die folgenden Typen:
- Zeilenabdeckung: Wie viele Codezeilen werden von den Tests abgedeckt.
- Funktionsabdeckung: Wie viele Funktionen oder Methoden werden von den Tests abgedeckt.
- Zweigabdeckung: Wie viele Codezweige werden von den Tests abgedeckt (z. B. if/else-Anweisungen).
- Anweisungsabdeckung: Wie viele Anweisungen im Code werden von den Tests abgedeckt.
Die Unit-Test-Abdeckung ist eine wichtige Metrik zur Messung der Qualität von Unit-Tests, aber nicht die einzige Metrik. Eine hohe Abdeckungsrate kann dabei helfen, Fehler im Code zu erkennen, garantiert jedoch nicht die Qualität des Codes. Eine niedrige Abdeckungsrate kann bedeuten, dass es ungetesteten Code gibt, möglicherweise mit unentdeckten Fehlern.
Das Bild unten zeigt die Ergebnisse der Unit-Test-Abdeckung für ein Demoprojekt:
Für Dateien wie Dienste und Controller ist es im Allgemeinen besser, eine höhere Unit-Test-Abdeckung zu haben, während für Dateien wie Module weder Unit-Tests geschrieben werden müssen noch möglich sind, da dies bedeutungslos ist. Das obige Bild stellt die Gesamtmetriken für die gesamte Unit-Test-Abdeckung dar. Wenn Sie die Testabdeckung für eine bestimmte Funktion anzeigen möchten, können Sie die Datei coverage/lcov-report/index.html im Stammverzeichnis des Projekts öffnen. Ich möchte beispielsweise die konkrete Testsituation für die Methode „validateUser“ sehen:
Wie zu sehen ist, beträgt die ursprüngliche Unit-Test-Abdeckung für die Methode „validateUser“ nicht 100 %, und es gibt immer noch zwei Codezeilen, die nicht ausgeführt wurden. Dies spielt jedoch keine große Rolle, da es die vier wichtigsten Verarbeitungsknoten nicht beeinträchtigt und man eine hohe Testabdeckung nicht eindimensional anstreben sollte.
Schreiben von E2E-Tests
In den Unit-Tests haben wir gezeigt, wie man Unit-Tests für jede Funktion der Funktion „validateUser()“ schreibt und dabei simulierte Daten verwendet, um sicherzustellen, dass jede Funktion getestet werden kann. Beim e2E-Testen müssen wir reale Benutzerszenarien simulieren, daher ist zum Testen eine Verbindung zu einer Datenbank erforderlich. Daher interagieren alle Methoden im auth.service.ts-Modul, die wir testen werden, mit der Datenbank.
Das Authentifizierungsmodul umfasst hauptsächlich die folgenden Funktionen:
- Anmeldung
- Anmelden
- Token-Aktualisierung
- Benutzerinformationen lesen
- Passwort ändern
- Benutzer löschen
E2E-Tests müssen diese sechs Funktionen einzeln testen, beginnend mit der Registrierung und endend mit dem Löschen von Benutzern. Während des Tests können wir einen dedizierten Testbenutzer erstellen, der die Tests durchführt, und diesen Testbenutzer dann nach Abschluss löschen, um keine unnötigen Informationen in der Testdatenbank zu hinterlassen.
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; } </useraccountdto>
Die beforeAll-Hook-Funktion wird ausgeführt, bevor alle Tests beginnen, sodass wir hier ein Testkonto TEST_USER_NAME registrieren können. Die AfterAll-Hook-Funktion wird ausgeführt, nachdem alle Tests beendet sind. Daher eignet es sich hier, das Testkonto TEST_USER_NAME zu löschen, und testet außerdem bequem die Registrierungs- und Löschfunktionen.
In den Komponententests des vorherigen Abschnitts haben wir relevante Komponententests rund um die Methode „validateUser“ geschrieben. Tatsächlich wird diese Methode während der Anmeldung ausgeführt, um zu überprüfen, ob das Konto und das Passwort des Benutzers korrekt sind. Daher wird dieser e2E-Test auch den Anmeldevorgang verwenden, um zu demonstrieren, wie e2E-Testfälle erstellt werden.
Der gesamte Anmeldetestprozess umfasst fünf kleine 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 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... }); }); </authservice></record>
Diese fünf Tests lauten wie folgt:
- Erfolgreiche Anmeldung, Rückgabe von 200
- Wenn der Benutzer nicht existiert, lösen Sie eine 401-Ausnahme aus
- Wenn kein Passwort oder Benutzername angegeben wird, wird eine 400-Ausnahme ausgelöst
- Melden Sie sich mit dem falschen Passwort an und lösen Sie eine 401-Ausnahme aus
- Wenn das Konto gesperrt ist, lösen Sie eine 401-Ausnahme aus
Jetzt beginnen wir mit dem Schreiben der e2E-Tests:
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); }); </authservice>
Das Schreiben von e2E-Testcode ist relativ einfach: Sie rufen einfach die Schnittstelle auf und überprüfen dann das Ergebnis. Für den erfolgreichen Anmeldetest müssen wir beispielsweise nur überprüfen, ob das zurückgegebene Ergebnis 200 ist.
Die ersten vier Tests sind recht einfach. Schauen wir uns nun einen etwas komplizierteren e2E-Test an, bei dem überprüft wird, ob ein Konto gesperrt ist.
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; } </useraccountdto>
Wenn sich ein Benutzer dreimal hintereinander nicht anmeldet, wird das Konto gesperrt. Daher können wir in diesem Test das Testkonto TEST_USER_NAME nicht verwenden, da dieses Konto bei erfolgreichem Test gesperrt wird und die folgenden Tests nicht fortgesetzt werden können. Wir müssen einen weiteren neuen Benutzer, TEST_USER_NAME2, speziell zum Testen der Kontosperre registrieren und diesen Benutzer löschen, nachdem der Test erfolgreich war. Wie Sie sehen, ist der Code für diesen e2E-Test ziemlich umfangreich und erfordert viel Auf- und Abbauarbeit, aber der eigentliche Testcode besteht nur aus diesen wenigen Zeilen:
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 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... }); }); </authservice></record>
Das Schreiben von e2E-Testcode ist relativ einfach. Sie müssen keine Scheindaten oder Testabdeckung berücksichtigen. Es reicht aus, wenn der gesamte Systemprozess wie erwartet abläuft.
Ob Tests geschrieben werden sollen
Wenn möglich, empfehle ich generell, Tests zu schreiben. Dies kann die Robustheit, Wartbarkeit und Entwicklungseffizienz des Systems verbessern.
Verbesserung der Systemrobustheit
Beim Schreiben von Code konzentrieren wir uns normalerweise auf den Programmablauf bei normalen Eingaben, um sicherzustellen, dass die Kernfunktionalität ordnungsgemäß funktioniert. Allerdings übersehen wir oft einige Randfälle, wie zum Beispiel abnormale Eingaben. Das Schreiben von Tests ändert dies; Es zwingt Sie dazu, darüber nachzudenken, wie Sie mit diesen Fällen umgehen und angemessen reagieren, um so Abstürze zu verhindern. Man kann sagen, dass das Schreiben von Tests indirekt die Robustheit des Systems verbessert.
Verbesserung der Wartbarkeit
Ein neues Projekt zu übernehmen, das umfassende Tests beinhaltet, kann sehr angenehm sein. Sie dienen als Leitfaden und helfen Ihnen, die verschiedenen Funktionalitäten schnell zu verstehen. Wenn Sie sich einfach den Testcode ansehen, können Sie das erwartete Verhalten und die Randbedingungen jeder Funktion leicht erfassen, ohne jede Zeile des Funktionscodes durchgehen zu müssen.
Steigerung der Entwicklungseffizienz
Stellen Sie sich vor, ein Projekt, das eine Weile nicht aktualisiert wurde, erhält plötzlich neue Anforderungen. Nachdem Sie Änderungen vorgenommen haben, befürchten Sie möglicherweise, dass Fehler auftreten. Ohne Tests müssten Sie das gesamte Projekt erneut manuell testen – was Zeitverschwendung und Ineffizienz wäre. Bei vollständigen Tests können Sie mit einem einzigen Befehl feststellen, ob sich die Codeänderungen auf bestehende Funktionalitäten ausgewirkt haben. Auch wenn es Fehler gibt, können diese schnell lokalisiert und behoben werden.
Wann sollte man keine Tests schreiben?
Für kurzfristige Projekte und Projekte mit sehr schnellen Anforderungsiterationen wird das Schreiben von Tests nicht empfohlen. Beispielsweise sind für einige Projekte, die für Veranstaltungen gedacht sind, die nach Ende der Veranstaltung unbrauchbar sind, keine Tests erforderlich. Auch für Projekte, die sehr schnelle Anforderungsiterationen durchlaufen, habe ich gesagt, dass das Schreiben von Tests die Entwicklungseffizienz verbessern könnte, aber das basiert auf der Prämisse, dass Funktionsiterationen langsam sind. Wenn sich die gerade abgeschlossene Funktion in ein oder zwei Tagen ändert, muss der zugehörige Testcode neu geschrieben werden. Daher ist es besser, überhaupt keine Tests zu schreiben und sich stattdessen auf das Testteam zu verlassen, denn das Schreiben von Tests ist sehr zeitaufwändig und die Mühe nicht wert.
Abschluss
Nachdem ich ausführlich erklärt habe, wie man Unit-Tests und e2E-Tests für NestJS-Projekte schreibt, möchte ich noch einmal auf die Bedeutung des Testens hinweisen. Es kann die Robustheit, Wartbarkeit und Entwicklungseffizienz des Systems verbessern. Wenn Sie keine Möglichkeit haben, Tests zu schreiben, empfehle ich Ihnen, selbst ein Übungsprojekt zu starten oder an einigen Open-Source-Projekten teilzunehmen und Code dazu beizutragen. Für Open-Source-Projekte gelten im Allgemeinen strengere Codeanforderungen. Wenn Sie Code beisteuern, müssen Sie möglicherweise neue Testfälle schreiben oder bestehende ändern.
Referenzmaterialien
- NestJS: Ein Framework zum Erstellen effizienter, skalierbarer serverseitiger Node.js-Anwendungen.
- MongoDB: Eine NoSQL-Datenbank zur Datenspeicherung.
- Jest: Ein Testframework für JavaScript und TypeScript.
- Supertest: Eine Bibliothek zum Testen von HTTP-Servern.
Das obige ist der detaillierte Inhalt vonSo schreiben Sie Unit-Tests und Etests für NestJS-Anwendungen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Unterschiedliche JavaScript -Motoren haben unterschiedliche Auswirkungen beim Analysieren und Ausführen von JavaScript -Code, da sich die Implementierungsprinzipien und Optimierungsstrategien jeder Engine unterscheiden. 1. Lexikalanalyse: Quellcode in die lexikalische Einheit umwandeln. 2. Grammatikanalyse: Erzeugen Sie einen abstrakten Syntaxbaum. 3. Optimierung und Kompilierung: Generieren Sie den Maschinencode über den JIT -Compiler. 4. Führen Sie aus: Führen Sie den Maschinencode aus. V8 Engine optimiert durch sofortige Kompilierung und versteckte Klasse.

Zu den Anwendungen von JavaScript in der realen Welt gehören die serverseitige Programmierung, die Entwicklung mobiler Anwendungen und das Internet der Dinge. Die serverseitige Programmierung wird über node.js realisiert, die für die hohe gleichzeitige Anfrageverarbeitung geeignet sind. 2. Die Entwicklung der mobilen Anwendungen erfolgt durch reaktnative und unterstützt die plattformübergreifende Bereitstellung. 3.. Wird für die Steuerung von IoT-Geräten über die Johnny-Five-Bibliothek verwendet, geeignet für Hardware-Interaktion.

Ich habe eine funktionale SaaS-Anwendung mit mehreren Mandanten (eine EdTech-App) mit Ihrem täglichen Tech-Tool erstellt und Sie können dasselbe tun. Was ist eine SaaS-Anwendung mit mehreren Mietern? Mit Multi-Tenant-SaaS-Anwendungen können Sie mehrere Kunden aus einem Sing bedienen

Dieser Artikel zeigt die Frontend -Integration mit einem Backend, das durch die Genehmigung gesichert ist und eine funktionale edtech SaaS -Anwendung unter Verwendung von Next.js. erstellt. Die Frontend erfasst Benutzerberechtigungen zur Steuerung der UI-Sichtbarkeit und stellt sicher, dass API-Anfragen die Rollenbasis einhalten

JavaScript ist die Kernsprache der modernen Webentwicklung und wird für seine Vielfalt und Flexibilität häufig verwendet. 1) Front-End-Entwicklung: Erstellen Sie dynamische Webseiten und einseitige Anwendungen durch DOM-Operationen und moderne Rahmenbedingungen (wie React, Vue.js, Angular). 2) Serverseitige Entwicklung: Node.js verwendet ein nicht blockierendes E/A-Modell, um hohe Parallelitäts- und Echtzeitanwendungen zu verarbeiten. 3) Entwicklung von Mobil- und Desktop-Anwendungen: Die plattformübergreifende Entwicklung wird durch reaktnative und elektronen zur Verbesserung der Entwicklungseffizienz realisiert.

Zu den neuesten Trends im JavaScript gehören der Aufstieg von Typenkripten, die Popularität moderner Frameworks und Bibliotheken und die Anwendung der WebAssembly. Zukunftsaussichten umfassen leistungsfähigere Typsysteme, die Entwicklung des serverseitigen JavaScript, die Erweiterung der künstlichen Intelligenz und des maschinellen Lernens sowie das Potenzial von IoT und Edge Computing.

JavaScript ist der Eckpfeiler der modernen Webentwicklung. Zu den Hauptfunktionen gehören eine ereignisorientierte Programmierung, die Erzeugung der dynamischen Inhalte und die asynchrone Programmierung. 1) Ereignisgesteuerte Programmierung ermöglicht es Webseiten, sich dynamisch entsprechend den Benutzeroperationen zu ändern. 2) Die dynamische Inhaltsgenerierung ermöglicht die Anpassung der Seiteninhalte gemäß den Bedingungen. 3) Asynchrone Programmierung stellt sicher, dass die Benutzeroberfläche nicht blockiert ist. JavaScript wird häufig in der Webinteraktion, der einseitigen Anwendung und der serverseitigen Entwicklung verwendet, wodurch die Flexibilität der Benutzererfahrung und die plattformübergreifende Entwicklung erheblich verbessert wird.

Python eignet sich besser für Datenwissenschaft und maschinelles Lernen, während JavaScript besser für die Entwicklung von Front-End- und Vollstapel geeignet ist. 1. Python ist bekannt für seine prägnante Syntax- und Rich -Bibliotheks -Ökosystems und ist für die Datenanalyse und die Webentwicklung geeignet. 2. JavaScript ist der Kern der Front-End-Entwicklung. Node.js unterstützt die serverseitige Programmierung und eignet sich für die Entwicklung der Vollstapel.


Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

Dreamweaver CS6
Visuelle Webentwicklungstools

Sicherer Prüfungsbrowser
Safe Exam Browser ist eine sichere Browserumgebung für die sichere Teilnahme an Online-Prüfungen. Diese Software verwandelt jeden Computer in einen sicheren Arbeitsplatz. Es kontrolliert den Zugriff auf alle Dienstprogramme und verhindert, dass Schüler nicht autorisierte Ressourcen nutzen.

EditPlus chinesische Crack-Version
Geringe Größe, Syntaxhervorhebung, unterstützt keine Code-Eingabeaufforderungsfunktion

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

WebStorm-Mac-Version
Nützliche JavaScript-Entwicklungstools