Baru-baru ini, saya telah menulis ujian unit dan ujian E2E untuk projek NestJS. Ini adalah kali pertama saya menulis ujian untuk projek bahagian belakang, dan saya mendapati prosesnya berbeza daripada pengalaman saya dengan ujian bahagian hadapan, menjadikannya mencabar untuk bermula. Selepas melihat beberapa contoh, saya telah mendapat pemahaman yang lebih jelas tentang cara mendekati ujian, jadi saya bercadang untuk menulis artikel untuk merekod dan berkongsi pembelajaran saya untuk membantu orang lain yang mungkin menghadapi kekeliruan yang sama.
Selain itu, saya telah menyusun projek tunjuk cara dengan unit yang berkaitan dan ujian E2E selesai, yang mungkin menarik minat. Kod telah dimuat naik ke Github: https://github.com/woai3c/nestjs-demo.
Ujian unit dan ujian E2E ialah kaedah ujian perisian, tetapi ia mempunyai matlamat dan skop yang berbeza.
Ujian unit melibatkan pemeriksaan dan pengesahan unit terkecil yang boleh diuji dalam perisian. Fungsi atau kaedah, sebagai contoh, boleh dianggap sebagai unit. Dalam ujian unit, anda menyediakan output yang dijangkakan untuk pelbagai input fungsi dan mengesahkan ketepatan operasinya. Matlamat ujian unit adalah untuk mengenal pasti pepijat dalam fungsi dengan cepat dan ia mudah untuk ditulis dan dilaksanakan dengan cepat.
Sebaliknya, ujian E2E sering mensimulasikan senario pengguna dunia sebenar untuk menguji keseluruhan aplikasi. Sebagai contoh, bahagian hadapan biasanya menggunakan penyemak imbas atau penyemak imbas tanpa kepala untuk ujian, manakala bahagian belakang melakukannya dengan mensimulasikan panggilan API.
Dalam projek NestJS, ujian unit mungkin menilai perkhidmatan tertentu atau kaedah pengawal, seperti mengesahkan sama ada kaedah kemas kini dalam modul Pengguna mengemas kini pengguna dengan betul. Ujian E2E, walau bagaimanapun, mungkin meneliti perjalanan pengguna yang lengkap, daripada mencipta pengguna baharu kepada mengemas kini kata laluan mereka dan akhirnya memadamkan pengguna, yang melibatkan berbilang perkhidmatan dan pengawal.
Menulis ujian unit untuk fungsi utiliti atau kaedah yang tidak melibatkan antara muka adalah agak mudah. Anda hanya perlu mempertimbangkan pelbagai input dan tulis kod ujian yang sepadan. Walau bagaimanapun, keadaan menjadi lebih kompleks apabila antara muka mula dimainkan. Mari gunakan kod sebagai contoh:
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; }
Kod di atas ialah kaedah validateUser dalam fail auth.service.ts, terutamanya digunakan untuk mengesahkan sama ada nama pengguna dan kata laluan yang dimasukkan oleh pengguna semasa log masuk adalah betul. Ia mengandungi logik berikut:
Seperti yang dapat dilihat, kaedah validateUser merangkumi empat logik pemprosesan dan kami perlu menulis kod ujian unit yang sepadan untuk empat mata ini untuk memastikan keseluruhan fungsi validateUser beroperasi dengan betul.
Apabila kami mula menulis ujian unit, kami menghadapi masalah: kaedah findOne perlu berinteraksi dengan pangkalan data dan ia mencari pengguna yang sepadan dalam pangkalan data melalui nama pengguna. Walau bagaimanapun, jika setiap ujian unit perlu berinteraksi dengan pangkalan data, ujian akan menjadi sangat rumit. Oleh itu, kita boleh mengejek data palsu untuk mencapai matlamat ini.
Sebagai contoh, anggap kami telah mendaftarkan pengguna bernama woai3c. Kemudian, semasa log masuk, data pengguna boleh diambil dalam kaedah validateUser melalui const entity = await this.usersService.findOne({ username });. Selagi baris kod ini boleh mengembalikan data yang dikehendaki, tiada masalah, walaupun tanpa interaksi pangkalan data. Kita boleh mencapai ini melalui data palsu. Sekarang, mari lihat kod ujian yang berkaitan untuk kaedah 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; }
Kami mendapat data pengguna dengan memanggil kaedah findOne usersService, jadi kami perlu mengejek kaedah findOne usersService dalam kod ujian:
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... }); });
Kami menggunakan jest.fn() untuk mengembalikan fungsi bagi menggantikan usersService.findOne() sebenar. Jika usersService.findOne() dipanggil sekarang, tidak akan ada nilai pulangan, jadi kes ujian unit pertama akan lulus:
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); });
Memandangkan findOne dalam entiti const = tunggu this.usersService.findOne({ nama pengguna }); daripada kaedah validateUser ialah fungsi palsu yang dipermainkan tanpa nilai pulangan, baris ke-2 hingga ke-4 kod dalam kaedah validateUser boleh dilaksanakan:
it('should throw an UnauthorizedException if user is not found', async () => { await expect( authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD), ).rejects.toThrow(UnauthorizedException); });
Lemparkan ralat 401, seperti yang dijangkakan.
Logik kedua dalam kaedah validateUser adalah untuk menentukan sama ada pengguna dikunci, dengan kod yang sepadan seperti berikut:
if (!entity) { throw new UnauthorizedException('User not found'); }
Seperti yang anda lihat, kami boleh menentukan bahawa akaun semasa dikunci jika terdapat kunci masa kunciSehingga dalam data pengguna dan masa tamat kunci lebih besar daripada masa semasa. Oleh itu, kita perlu mengejek data pengguna dengan medan 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; }
Dalam kod ujian di atas, objek dikunciUser pertama kali ditakrifkan, yang mengandungi medan lockSehingga yang kami perlukan. Kemudian, ia digunakan sebagai nilai pulangan untuk findOne, dicapai oleh usersService.findOne.mockResolvedValueOnce(lockedUser);. Oleh itu, apabila kaedah validateUser dilaksanakan, data pengguna di dalamnya adalah data olok-olok, berjaya membenarkan kes ujian kedua lulus.
Liputan ujian unit (Liputan Kod) ialah metrik yang digunakan untuk menerangkan jumlah kod aplikasi yang telah dilindungi atau diuji oleh ujian unit. Ia biasanya dinyatakan sebagai peratusan, menunjukkan jumlah semua laluan kod yang mungkin telah diliputi oleh kes ujian.
Liputan ujian unit biasanya termasuk jenis berikut:
Liputan ujian unit ialah metrik penting untuk mengukur kualiti ujian unit, tetapi ia bukan satu-satunya metrik. Kadar liputan yang tinggi boleh membantu untuk mengesan ralat dalam kod, tetapi ia tidak menjamin kualiti kod. Kadar liputan yang rendah mungkin bermakna terdapat kod yang belum diuji, berkemungkinan dengan ralat yang tidak dapat dikesan.
Imej di bawah menunjukkan keputusan liputan ujian unit untuk projek tunjuk cara:
Untuk fail seperti perkhidmatan dan pengawal, secara amnya lebih baik untuk mempunyai liputan ujian unit yang lebih tinggi, manakala untuk fail seperti modul tidak perlu menulis ujian unit, dan tidak mungkin untuk menulisnya, kerana ia tidak bermakna. Imej di atas mewakili metrik keseluruhan untuk keseluruhan liputan ujian unit. Jika anda ingin melihat liputan ujian untuk fungsi tertentu, anda boleh membuka fail coverage/lcov-report/index.html dalam direktori akar projek. Sebagai contoh, saya ingin melihat situasi ujian khusus untuk kaedah validateUser:
Seperti yang dapat dilihat, liputan ujian unit asal untuk kaedah validateUser bukanlah 100%, dan masih terdapat dua baris kod yang tidak dilaksanakan. Walau bagaimanapun, ia tidak begitu penting, kerana ia tidak menjejaskan empat nod pemprosesan utama dan seseorang itu tidak seharusnya mengejar liputan ujian tinggi secara unidimensi.
Dalam ujian unit, kami menunjukkan cara menulis ujian unit untuk setiap ciri fungsi validateUser(), menggunakan data yang dipermainkan untuk memastikan setiap ciri boleh diuji. Dalam ujian e2E, kita perlu mensimulasikan senario pengguna sebenar, jadi penyambungan ke pangkalan data untuk ujian adalah perlu. Oleh itu, kaedah dalam modul auth.service.ts yang akan kami uji semuanya berinteraksi dengan pangkalan data.
Modul pengesahan terutamanya termasuk ciri berikut:
Ujian E2E perlu menguji enam ciri ini satu demi satu, bermula dengan pendaftaran dan berakhir dengan pemadaman pengguna. Semasa ujian, kami boleh mencipta pengguna ujian yang berdedikasi untuk menjalankan ujian dan kemudian memadamkan pengguna ujian ini setelah selesai, supaya tidak meninggalkan sebarang maklumat yang tidak diperlukan dalam pangkalan data ujian.
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; }
Fungsi cangkuk beforeAll dijalankan sebelum semua ujian bermula, jadi kami boleh mendaftar akaun ujian TEST_USER_NAME di sini. Fungsi cangkuk afterAll berjalan selepas semua ujian tamat, jadi adalah sesuai untuk memadamkan akaun ujian TEST_USER_NAME di sini dan ia juga menguji fungsi pendaftaran dan pemadaman dengan mudah.
Dalam ujian unit bahagian sebelumnya, kami menulis ujian unit yang berkaitan di sekitar kaedah validateUser. Sebenarnya, kaedah ini dilaksanakan semasa log masuk untuk mengesahkan sama ada akaun dan kata laluan pengguna adalah betul. Oleh itu, ujian e2E ini juga akan menggunakan proses log masuk untuk menunjukkan cara mengarang kes ujian e2E.
Keseluruhan proses ujian log masuk termasuk lima ujian kecil:
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... }); });
Lima ujian ini adalah seperti berikut:
Sekarang mari mula menulis ujian 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); });
Menulis kod ujian e2E agak mudah: anda hanya memanggil antara muka dan kemudian mengesahkan hasilnya. Contohnya, untuk ujian log masuk yang berjaya, kami hanya perlu mengesahkan bahawa hasil yang dikembalikan ialah 200.
Empat ujian pertama agak mudah. Sekarang mari kita lihat ujian e2E yang lebih rumit, iaitu untuk mengesahkan sama ada akaun dikunci.
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; }
Apabila pengguna gagal log masuk tiga kali berturut-turut, akaun akan dikunci. Oleh itu, dalam ujian ini, kami tidak boleh menggunakan akaun ujian TEST_USER_NAME, kerana jika ujian berjaya, akaun ini akan dikunci dan tidak dapat meneruskan ujian berikut. Kami perlu mendaftarkan pengguna baharu TEST_USER_NAME2 yang lain secara khusus untuk menguji penguncian akaun dan memadamkan pengguna ini selepas ujian berjaya. Jadi, seperti yang anda boleh lihat, kod untuk ujian e2E ini agak besar, memerlukan banyak persediaan dan kerja pembongkaran, tetapi kod ujian sebenar hanyalah beberapa baris berikut:
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... }); });
Menulis kod ujian e2E agak mudah. Anda tidak perlu mempertimbangkan data palsu atau liputan ujian. Ia memadai jika keseluruhan proses sistem berjalan seperti yang diharapkan.
Jika boleh, saya biasanya mengesyorkan ujian menulis. Melakukannya boleh meningkatkan keteguhan, kebolehselenggaraan dan kecekapan pembangunan sistem.
Apabila menulis kod, kami biasanya menumpukan pada aliran program di bawah input biasa untuk memastikan fungsi teras berfungsi dengan baik. Walau bagaimanapun, kita mungkin sering terlepas pandang beberapa kes kelebihan, seperti input yang tidak normal. Ujian penulisan mengubah ini; ia memaksa anda untuk mempertimbangkan cara mengendalikan kes ini dan bertindak balas dengan sewajarnya, sekali gus mengelakkan ranap sistem. Boleh dikatakan ujian penulisan secara tidak langsung meningkatkan kekukuhan sistem.
Mengambil alih projek baharu yang merangkumi ujian komprehensif boleh menjadi sangat menyenangkan. Mereka bertindak sebagai panduan, membantu anda memahami pelbagai fungsi dengan cepat. Hanya dengan melihat kod ujian, anda boleh memahami dengan mudah tingkah laku yang dijangkakan dan syarat sempadan setiap fungsi tanpa perlu melalui setiap baris kod fungsi tersebut.
Bayangkan, projek yang sudah lama tidak dikemas kini tiba-tiba menerima keperluan baharu. Selepas membuat perubahan, anda mungkin bimbang tentang memperkenalkan pepijat. Tanpa ujian, anda perlu menguji keseluruhan projek secara manual sekali lagi — membuang masa dan menjadi tidak cekap. Dengan ujian lengkap, satu arahan boleh memberitahu anda sama ada perubahan kod telah memberi kesan kepada fungsi sedia ada. Walaupun terdapat ralat, ia dapat dikesan dan ditangani dengan cepat.
Untuk projek jangka pendek dan projek dengan lelaran keperluan yang sangat pantas, tidak disyorkan untuk menulis ujian. Contohnya, beberapa projek yang dimaksudkan untuk acara yang tidak berguna selepas acara tamat tidak memerlukan ujian. Selain itu, untuk projek yang menjalani lelaran keperluan yang sangat pantas, saya mengatakan bahawa ujian penulisan boleh meningkatkan kecekapan pembangunan, tetapi itu berdasarkan premis bahawa lelaran fungsi adalah perlahan. Jika fungsi yang baru anda selesaikan berubah dalam satu atau dua hari, kod ujian yang berkaitan mesti ditulis semula. Jadi, adalah lebih baik untuk tidak menulis ujian sama sekali dan bergantung pada pasukan ujian kerana ujian menulis sangat memakan masa dan tidak berbaloi dengan usaha.
Selepas menerangkan secara terperinci cara menulis ujian unit dan ujian e2E untuk projek NestJS, saya masih ingin mengulangi kepentingan ujian. Ia boleh meningkatkan keteguhan, kebolehselenggaraan, dan kecekapan pembangunan sistem. Jika anda tidak mempunyai peluang untuk menulis ujian, saya cadangkan anda memulakan projek latihan sendiri atau mengambil bahagian dalam beberapa projek sumber terbuka dan menyumbang kod kepada mereka. Projek sumber terbuka biasanya mempunyai keperluan kod yang lebih ketat. Kod penyumbang mungkin memerlukan anda menulis kes ujian baharu atau mengubah suai yang sedia ada.
Atas ialah kandungan terperinci Cara menulis ujian unit dan Etest untuk aplikasi NestJS. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!