Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Menguji kode integrasi Anda untuk Azure SDK for JavaScript sangat penting untuk memastikan aplikasi Anda berinteraksi dengan benar dengan layanan Azure. Panduan ini menunjukkan kepada Anda cara menguji integrasi Azure SDK secara efektif di aplikasi JavaScript Anda dengan menggunakan kerangka kerja pengujian.
Saat memutuskan apakah akan meniru panggilan SDK layanan cloud atau menggunakan layanan langsung untuk tujuan pengujian, penting untuk mempertimbangkan trade-off antara kecepatan, keandalan, dan biaya. Artikel ini menunjukkan cara menggunakan kerangka kerja pengujian untuk menguji integrasi SDK. Kode aplikasi menyisipkan dokumen ke Cosmos DB. Kode pengujian meniadakan penggunaan sumber daya tersebut sehingga sumber daya cloud tidak digunakan.
Kerangka kerja yang digunakan adalah:
- Jest dengan CommonJs
- Vitest dengan ESM
- Node.js Uji runner dengan ESM
Prasyarat
Node.js LTS. Status rilis LTS adalah "dukungan jangka panjang", yang biasanya menjamin bahwa bug penting akan diperbaiki selama total 30 bulan.
Penguji tesNode.js adalah bagian dari penginstalan Node.js.
Perhatian
Sampel yang disediakan untuk Node.js test runner menggunakan modul eksperimen node:test dengan mock.fn(). Perlu diingat bahwa penguji bawaan Node belum menawarkan API pemalsuan yang didukung penuh. Pastikan versi Node target Anda mendukung API eksperimental atau pertimbangkan untuk menggunakan pustaka tiruan pihak ketiga (atau fungsi stub) sebagai gantinya.
Mengejek layanan cloud
Keuntungan:
- Mempercepat rangkaian pengujian dengan menghilangkan latensi jaringan.
- Menyediakan lingkungan pengujian yang dapat diprediksi dan dikontrol.
- Lebih mudah untuk mensimulasikan berbagai skenario dan kasus tepi.
- Mengurangi biaya yang terkait dengan penggunaan layanan cloud langsung, terutama dalam alur integrasi berkelanjutan.
Kontra:
- Tiruan dapat berbelok dari SDK yang sebenarnya, yang menyebabkan ketidaksesuaian.
- Mungkin mengabaikan fitur atau perilaku tertentu dari layanan langsung.
- Lingkungan yang kurang realistis dibandingkan dengan lingkungan produksi.
Menggunakan layanan langsung
Keuntungan:
- Apakah lingkungan realistis yang mencerminkan produksi secara dekat?
- Apakah berguna untuk pengujian integrasi untuk memastikan berbagai bagian sistem bekerja sama?
- Apakah berguna untuk mengidentifikasi masalah yang terkait dengan keandalan jaringan, ketersediaan layanan, dan penanganan data aktual?
Kontra:
- Lebih lambat karena adanya panggilan jaringan.
- Lebih mahal karena potensi biaya penggunaan layanan.
- Merupakan hal yang kompleks dan memakan waktu untuk menyiapkan dan memelihara lingkungan layanan langsung yang cocok dengan produksi.
Pilihan antara menirukan dan menggunakan layanan langsung tergantung pada strategi pengujian Anda. Untuk pengujian unit di mana kecepatan dan kontrol sangat penting, meniru sering kali merupakan pilihan yang lebih baik. Untuk pengujian integrasi di mana realisme sangat penting, menggunakan layanan langsung dapat memberikan hasil yang lebih akurat. Menyeimbangkan pendekatan ini membantu mencapai cakupan pengujian yang komprehensif sambil mengelola biaya dan menjaga efisiensi pengujian.
Uji ganda: Tiruan, bongkahan, dan palsu
Uji ganda adalah pengganti apa pun yang digunakan untuk menggantikan sesuatu yang nyata untuk tujuan pengujian. Jenis ganda yang Anda pilih didasarkan pada apa yang Anda inginkan untuk menggantinya. Istilah tiruan sering dimaksudkan sebagai ganda ketika istilah ini digunakan secara santai. Dalam artikel ini, istilah ini digunakan secara khusus dan diilustrasikan secara khusus dalam kerangka kerja pengujian Jest.
Ejekan
Tiruan (juga disebut mata-mata): Menggantikan dalam fungsi dan dapat mengontrol serta memata-matai perilaku fungsi tersebut saat dipanggil secara tidak langsung oleh kode lain.
Dalam contoh berikut, Anda memiliki 2 fungsi:
-
someTestFunction : Fungsi yang perlu Anda uji. Ini memanggil dependensi,
dependencyFunction
, yang tidak Anda tulis dan tidak perlu diuji. - dependencyFunctionMock: Mock dari dependensi.
import { mock } from 'node:test';
import assert from 'node:assert';
// ARRANGE
const dependencyFunctionMock = mock.fn();
// ACT
// Mock replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()
// ASSERT
assert.strictEqual(dependencyFunctionMock.mock.callCount(), 1);
Tujuan pengujian adalah untuk memastikan bahwa someTestFunction berperilaku dengan benar tanpa benar-benar memanggil kode dependensi. Pengujian memvalidasi bahwa tiruan dependensi dipanggil.
Menipu dependensi besar versus kecil
Ketika Anda memutuskan untuk memalsukan dependensi, Anda dapat memilih untuk memalsukan hanya yang diperlukan seperti:
- Beberapa fungsi dari dependensi yang lebih besar. Jest menawarkan tiruan parsial untuk tujuan ini.
- Semua fungsi dari dependensi yang lebih kecil, seperti yang ditunjukkan dalam contoh di artikel ini.
Stub
Tujuan dari stub adalah untuk mengganti data pengembalian fungsi untuk mensimulasikan skenario yang berbeda. Anda menggunakan stub untuk memungkinkan kode Anda memanggil fungsi dan menerima berbagai keadaan, termasuk hasil yang berhasil, kegagalan, pengecualian, dan kasus tepi. Verifikasi kondisi memastikan kode Anda menangani skenario ini secara tepat.
import { describe, it, beforeEach, mock } from 'node:test';
import assert from 'node:assert';
// ARRANGE
const fakeDatabaseData = {first: 'John', last: 'Jones'};
const dependencyFunctionMock = mock.fn();
dependencyFunctionMock.mock.mockImplementation((arg) => {
return fakeDatabaseData;
});
// ACT
// Mock replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()
// ASSERT
assert.strictEqual(name, `${fakeDatabaseData.first} ${fakeDatabaseData.last}`);
Tujuan dari pengujian sebelumnya adalah untuk memastikan bahwa pekerjaan yang dilakukan dengan someTestFunction
memenuhi hasil yang diharapkan. Dalam contoh sederhana ini, tugas fungsi adalah menggabungkan nama depan dan keluarga. Dengan menggunakan data palsu, Anda mengetahui hasil yang diharapkan dan dapat memvalidasi bahwa fungsi melakukan pekerjaan dengan benar.
Palsu
Palsu menggantikan fungsionalitas yang biasanya tidak akan Anda gunakan dalam produksi, seperti menggunakan database dalam memori alih-alih database cloud.
// fake-in-mem-db.spec.ts
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
import assert from 'node:assert';
class FakeDatabase {
private data: Record<string, any>;
constructor() {
this.data = {};
}
save(key: string, value: any): void {
this.data[key] = value;
}
get(key: string): any {
return this.data[key];
}
}
// Function to test
function someTestFunction(db: FakeDatabase, key: string, value: any): any {
db.save(key, value);
return db.get(key);
}
describe('In-Mem DB', () => {
let fakeDb: FakeDatabase;
let testKey: string;
let testValue: any;
beforeEach(() => {
fakeDb = new FakeDatabase();
testKey = 'testKey';
testValue = {
first: 'John',
last: 'Jones',
lastUpdated: new Date().toISOString(),
};
});
afterEach(() => {
// Restore all mocks created by node:test’s mock helper.
mock.restoreAll();
});
it('should save and return the correct value', () => {
// Create a spy on the save method using node:test's mock helper.
const saveSpy = mock.method(fakeDb, 'save').mock;
// Call the function under test.
const result = someTestFunction(fakeDb, testKey, testValue);
// Verify state.
assert.deepStrictEqual(result, testValue);
assert.strictEqual(result.first, 'John');
assert.strictEqual(result.last, 'Jones');
assert.strictEqual(result.lastUpdated, testValue.lastUpdated);
// Verify behavior
assert.strictEqual(saveSpy.callCount(), 1);
const calls = saveSpy.calls;
assert.deepStrictEqual(calls[0].arguments, [testKey, testValue]);
});
});
Tujuan dari pengujian sebelumnya adalah untuk memastikan bahwa someTestFunction
berinteraksi dengan database dengan benar. Dengan menggunakan database dalam memori palsu, Anda dapat menguji logika fungsi tanpa mengandalkan database nyata, membuat pengujian lebih cepat dan lebih andal.
Skenario: Menyisipkan dokumen ke Cosmos DB menggunakan Azure SDK
Bayangkan Anda memiliki aplikasi yang perlu menulis dokumen baru ke Cosmos DB jika semua informasi dikirimkan dan diverifikasi. Jika formulir kosong dikirimkan atau informasi tidak cocok dengan format yang diharapkan, aplikasi tidak boleh memasukkan data.
Cosmos DB digunakan sebagai contoh, namun konsepnya berlaku untuk sebagian besar Azure SDK untuk JavaScript. Fungsi berikut menangkap fungsionalitas ini:
// insertDocument.ts
import { Container } from '../data/connect-to-cosmos.js';
import type {
DbDocument,
DbError,
RawInput,
VerificationErrors,
} from '../data/model.js';
import Verify from '../data/verify.js';
export async function insertDocument(
container: Container,
doc: RawInput,
): Promise<DbDocument | DbError | VerificationErrors> {
const isVerified: boolean = Verify.inputVerified(doc);
if (!isVerified) {
return { message: 'Verification failed' } as VerificationErrors;
}
try {
const { resource } = await container.items.create({
id: doc.id,
name: `${doc.first} ${doc.last}`,
});
return resource as DbDocument;
} catch (error: any) {
if (error instanceof Error) {
if ((error as any).code === 409) {
return {
message: 'Insertion failed: Duplicate entry',
code: 409,
} as DbError;
}
return { message: error.message, code: (error as any).code } as DbError;
} else {
return { message: 'An unknown error occurred', code: 500 } as DbError;
}
}
}
Catatan
Jenis TypeScript membantu menentukan jenis data yang digunakan fungsi. Meskipun Anda tidak memerlukan TypeScript untuk menggunakan Jest atau kerangka kerja pengujian JavaScript lainnya, TypeScript penting untuk menulis JavaScript yang aman tipe.
Fungsi dalam aplikasi ini adalah:
Fungsi | Deskripsi |
---|---|
insertDocument | Menyisipkan dokumen ke dalam database. Inilah yang ingin kita uji. |
inputVerified | Memverifikasi data input terhadap skema. Memastikan data dalam format yang benar (misalnya, alamat email yang valid, URL yang diformat dengan benar). |
cosmos.items.create | Fungsi SDK untuk Azure Cosmos DB menggunakan @azure/cosmos. Inilah yang ingin kita ejek. Ini sudah memiliki tes sendiri yang dikelola oleh pemilik paket. Kita perlu memverifikasi bahwa panggilan fungsi Cosmos DB dilakukan dan mengembalikan data jika data masuk lolos verifikasi. |
Menginstal dependensi kerangka kerja pengujian
Kerangka kerja ini disediakan sebagai bagian dari Node.js LTS.
Mengonfigurasi paket untuk menjalankan pengujian
Perbarui package.json
aplikasi dengan skrip baru untuk menguji file kode sumber kami. File kode sumber didefinisikan dengan mencocokkan berdasarkan sebagian nama dan ekstensi file. Penyelenggara uji mencari file yang mengikuti konvensi penamaan umum untuk file pengujian: <file-name>.spec.[jt]s
. Pola ini berarti file yang bernama seperti contoh berikut ditafsirkan sebagai file pengujian dan dijalankan oleh pengelola uji:
- * .test.js: Misalnya, math.test.js
- * .spec.js: Misalnya, math.spec.js
- File yang terletak di direktori pengujian, seperti pengujian/math.js
Tambahkan skrip ke package.json untuk mendukung pola file pengujian tersebut dengan Runner uji:
"scripts": {
"test": "node --test --experimental-test-coverage --experimental-test-module-mocks --trace-exit"
}
Menyiapkan pengujian unit untuk Azure SDK
Bagaimana kita dapat menggunakan tiruan, stub, dan palsu untuk menguji fungsi insertDocument ?
- Tiruan: kita memerlukan tiruan untuk memastikan perilaku fungsi diuji seperti:
- Jika data melewati verifikasi, panggilan ke fungsi Cosmos DB hanya terjadi 1 kali
- Jika data tidak melewati verifikasi, panggilan ke fungsi Cosmos DB tidak terjadi
- Stub:
- Data yang diteruskan cocok dengan dokumen baru yang dikembalikan oleh fungsi.
Saat pengujian, pikirkan dalam hal pengaturan pengujian, pengujian itu sendiri, dan verifikasi. Dalam hal bahasa uji, fungsionalitas ini menggunakan istilah-istilah berikut:
- Susun: siapkan kondisi pengujian Anda
- Act: panggil fungsi Anda untuk menguji, juga dikenal sebagai system under test atau SUT
- Pernyataan: memvalidasi hasilnya. Hasilnya bisa berupa perilaku atau status.
- Perilaku menunjukkan fungsionalitas dalam fungsi pengujian Anda, yang dapat diverifikasi. Salah satu contohnya adalah bahwa beberapa dependensi dipanggil.
- Status menunjukkan data yang dikembalikan dari fungsi .
import { describe, it, afterEach, beforeEach, mock } from 'node:test';
import assert from 'node:assert';
describe('boilerplate', () => {
beforeEach(() => {
// Setup required before each test
});
afterEach(() => {
// Cleanup required after each test
});
it('should <do something> if <situation is present>', async () => {
// Arrange
// - set up the test data and the expected result
// Act
// - call the function to test
// Assert
// - check the state: result returned from function
// - check the behavior: dependency function calls
});
});
Saat Anda menggunakan tiruan dalam pengujian Anda, kode templat tersebut perlu menggunakan tiruan untuk menguji fungsi tanpa memanggil dependensi yang mendasari yang digunakan dalam fungsi, seperti pustaka klien Azure.
Membuat file uji
File pengujian dengan mock, guna mensimulasikan panggilan ke dependensi, memiliki pengaturan tambahan.
Ada beberapa bagian untuk file pengujian:
-
import
: Pernyataan impor memungkinkan Anda untuk menggunakan atau meniru salah satu dari setiap pengujian Anda. -
mock
: Buat perilaku tiruan default yang Anda inginkan. Setiap pengujian dapat mengubah sesuai kebutuhan. -
describe
: Kelompok pengujian keluarga untuk fileinsert.ts
. -
it
: Setiap pengujian untuk berkasinsert.ts
.
File pengujian mencakup tiga pengujian untuk insert.ts
file, yang dapat dibagi menjadi dua jenis validasi:
Jenis validasi | Uji |
---|---|
Selamat jalan: should insert document successfully |
Metode database yang ditiru dipanggil, dan mengembalikan data yang diubah. |
Jalur kesalahan: should return verification error if input is not verified |
Validasi data gagal dan mengembalikan kesalahan. |
Jalur kesalahan:should return error if db insert fails |
Metode database yang ditiru dipanggil, dan mengembalikan kesalahan. |
File pengujian berikut menunjukkan cara menguji fungsi insertDocument .
// insertDocument.test.ts
import { describe, it, beforeEach, mock } from 'node:test';
import assert from 'node:assert';
import { Container } from '../src/data/connect-to-cosmos.js';
import { createTestInputAndResult } from '../src/data/fake-data.js';
import type { DbDocument, DbError, RawInput } from '../src/data/model.js';
import { isDbError, isVerificationErrors } from '../src/data/model.js';
import Verify from '../src/data/verify.js';
import CosmosConnector from '../src/data/connect-to-cosmos.js';
import { insertDocument } from '../src/lib/insert.js';
describe('SDK', () => {
beforeEach(() => {
// Clear all mocks before each test
mock.restoreAll();
});
it('should return verification error if input is not verified', async () => {
const fakeContainer = {
items: {
create: async (_: any) => {
throw new Error('Create method not implemented');
},
},
} as unknown as Container;
const mVerify = mock.method(Verify, 'inputVerified').mock;
mVerify.mockImplementation(() => false);
const mGetUniqueId = mock.method(CosmosConnector, 'getUniqueId').mock;
mGetUniqueId.mockImplementation(() => 'unique-id');
const mContainerCreate = mock.method(fakeContainer.items, 'create').mock;
// Arrange: wrong shape of document on purpose.
const doc = { name: 'test' } as unknown as RawInput;
// Act:
const insertDocumentResult = await insertDocument(fakeContainer, doc);
// Assert - State verification.
if (isVerificationErrors(insertDocumentResult)) {
assert.deepStrictEqual(insertDocumentResult, {
message: 'Verification failed',
});
} else {
throw new Error('Result is not of type VerificationErrors');
}
// Assert - Behavior verification: Verify that create was never called.
assert.strictEqual(mContainerCreate.callCount(), 0);
});
it('should insert document successfully', async () => {
// Arrange: override inputVerified to return true.
const { input, result }: { input: RawInput; result: Partial<DbDocument> } =
createTestInputAndResult();
const fakeContainer = {
items: {
create: async (doc: any) => {
return { resource: result };
},
},
} as unknown as Container;
const mVerify = mock.method(Verify, 'inputVerified').mock;
mVerify.mockImplementation(() => true);
const mContainerCreate = mock.method(
fakeContainer.items as any,
'create',
).mock;
mContainerCreate.mockImplementation(async (doc: any) => {
return { resource: result };
});
// Act:
const receivedResult = await insertDocument(fakeContainer, input);
// Assert - State verification: Ensure the result is as expected.
assert.deepStrictEqual(receivedResult, result);
// Assert - Behavior verification: Ensure create was called once with correct arguments.
assert.strictEqual(mContainerCreate.callCount(), 1);
assert.deepStrictEqual(mContainerCreate.calls[0].arguments[0], {
id: input.id,
name: result.name,
});
});
it('should return error if db insert fails', async () => {
// Arrange: override inputVerified to return true.
const { input, result } = createTestInputAndResult();
const errorMessage: string = 'An unknown error occurred';
const fakeContainer = {
items: {
create: async (doc: any): Promise<any> => {
return Promise.resolve(null);
},
},
} as unknown as Container;
const mVerify = mock.method(Verify, 'inputVerified').mock;
mVerify.mockImplementation(() => true);
const mContainerCreate = mock.method(fakeContainer.items, 'create').mock;
mContainerCreate.mockImplementation(async (doc: any) => {
const mockError: DbError = {
message: errorMessage,
code: 500,
};
throw mockError;
});
// Act:
const insertDocumentResult = await insertDocument(fakeContainer, input);
// // Assert - Ensure create method was called once with the correct arguments.
assert.strictEqual(isDbError(insertDocumentResult), true);
assert.strictEqual(mContainerCreate.callCount(), 1);
assert.deepStrictEqual(mContainerCreate.calls[0].arguments[0], {
id: input.id,
name: result.name,
});
});
});
Pemecahan masalah
Sebagian besar kode dalam artikel ini berasal dari repositori GitHub MicrosoftDocs/node-essentials . Jika Anda ingin menyisipkan ke sumber daya Cosmos DB Cloud, buat sumber daya dengan skrip ini.