Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Testování integračního kódu pro sadu Azure SDK pro JavaScript je nezbytné k zajištění správné interakce vašich aplikací se službami Azure. V této příručce se dozvíte, jak efektivně otestovat integraci sady Azure SDK v javascriptových aplikacích s testovací architekturou.
Při rozhodování, zda simulovat volání API cloudové služby nebo použít živou službu pro účely testování, je důležité zvážit výhody proti nevýhodám mezi rychlostí, spolehlivostí a náklady. Tento článek ukazuje, jak použít testovací architekturu pro testování integrace sady SDK. Kód aplikace vloží dokument do cosmos DB. Testovací kód napodobí využití prostředků, aby se cloudový prostředek nepoužíval.
Použité architektury:
- Jest používající CommonJs
- Vitest s ESM
- Node.js Testovací runner s ESM
Požadavky
Node.js LTS. Stav verze LTS je dlouhodobá podpora, která obvykle zaručuje, že kritické chyby se opraví celkem po dobu 30 měsíců.
Node.js test runner je součástí instalace Node.js.
Upozornění
Ukázka poskytovaná pro spouštěč testů Node.js používá experimentální modul node:test s mock.fn(). Mějte na paměti, že Nodeův integrovaný testovací nástroj zatím nenabízí plně podporované rozhraní API pro mockování. Ujistěte se, že vaše cílová verze Node.js podporuje experimentální rozhraní API, nebo zvažte použití knihovny napodobování třetích stran (nebo zástupných funkcí).
Napodobování cloudových služeb
Výhody:
- Urychlí sadu testů tím, že eliminuje latenci sítě.
- Poskytuje předvídatelná a řízená testovací prostředí.
- Snadnější simulace různých scénářů a hraničních případů
- Snižuje náklady spojené s používáním živých cloudových služeb, zejména v kanálech kontinuální integrace.
Nevýhody:
- Napodobení se může od skutečné sady SDK lišit, což vede k nesrovnalostem.
- Může ignorovat určité funkce nebo chování živé služby.
- Méně reálné prostředí v porovnání s produkčním prostředím
Použití živé služby
Výhody:
- Je reálné prostředí, které věrně odráží produkci?
- Je užitečné při integračních testech zajistit spolupráci různých částí systému?
- Je užitečné identifikovat problémy související se spolehlivostí sítě, dostupností služeb a skutečným zpracováním dat?
Nevýhody:
- Je pomalejší kvůli síťovým voláním.
- Je dražší kvůli potenciálním nákladům na využití služeb.
- Nastavení a údržba živého prostředí služby, které odpovídá produkčnímu prostředí, je složité a časově náročné.
Volba mezi mockováním a používáním živých služeb závisí na vaší testovací strategii. U jednotkových testů, kde je rychlost a kontrola zásadní, je mockování často lepší volbou. V případě integračních testů, kde je realismus zásadní, může použití živé služby poskytovat přesnější výsledky. Vyvážení těchto přístupů pomáhá dosáhnout komplexního pokrytí testů při správě nákladů a zachování efektivity testů.
Testovací objekty: Mocky, stuby a falešné objekty
Testovací double je jakýkoli druh náhrady použité místo něčeho skutečného pro účely testování. Typ dvojčete záleží na tom, co chcete nahradit. Pojem napodobení se často označuje jako jakýkoliv dvojitý , když se termín používá příležitostně. V tomto článku se termín používá speciálně a ilustruje se konkrétně v testovacím rozhraní Jest.
Napodobeniny
Mocky (označované také jako špioni): Nahraďte funkci a buďte schopni řídit a špehovat chování této funkce, když je volána nepřímo jiným kódem.
V následujících příkladech máte 2 funkce:
-
someTestFunction: Funkce, kterou potřebujete otestovat. Volá závislost,
dependencyFunction
, kterou jste nenapsali a nepotřebujete testovat. - dependencyFunctionMock: Simulace závislosti.
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);
Účelem testu je zajistit, aby se funkce someTestFunction chovala správně, aniž by skutečně vyvolávala kód závislosti. Test ověří, že byl zavolán mock závislosti.
Napodobení velkých závislostí oproti malým závislostem
Když se rozhodnete napodobit závislost, můžete se rozhodnout napodobit jen to, co potřebujete, například:
- Funkce nebo dva z větší závislosti Jest nabízí pro tento účel částečné mockování.
- Všechny funkce menší závislosti, jak je znázorněno v příkladu v tomto článku.
Zástupné procedury
Účelem stubů je nahradit návratová data funkce k simulaci různých scénářů. Použijete zástup, abyste umožnili vašemu kódu volat funkci a přijímat různé stavové hodnoty, včetně úspěšných výsledků, selhání, výjimek a hraničních případů. Ověření stavu zajišťuje, že váš kód zpracovává tyto scénáře správně.
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}`);
Účelem předchozího testu je zajistit, aby práce provedená someTestFunction
splňovala očekávaný výsledek. V tomto jednoduchém příkladu je úkolem funkce zřetězit křestní jméno a příjmení. Pomocí falešných dat znáte očekávaný výsledek a můžete ověřit, že funkce funguje správně.
Padělky
Fakes nahrazují funkci, kterou byste normálně nepoužíli v produkčním prostředí, jako je například použití databáze v paměti místo cloudové databáze.
// 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]);
});
});
Účelem předchozího testu je zajistit správnou someTestFunction
interakci s databází. Pomocí falešné databáze v paměti můžete otestovat logiku funkce, aniž byste se museli spoléhat na skutečnou databázi, aby testy byly rychlejší a spolehlivější.
Scénář: Vložení dokumentu do služby Cosmos DB pomocí sady Azure SDK
Představte si, že máte aplikaci, která potřebuje zapsat nový dokument do služby Cosmos DB , pokud jsou všechny informace odeslány a ověřeny. Pokud se odešle prázdný formulář nebo informace neodpovídají očekávanému formátu, aplikace by neměla zadávat data.
Cosmos DB se používá jako příklad, ale koncepty platí pro většinu sad Azure SDK pro JavaScript. Následující funkce zachycuje tuto funkci:
// 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;
}
}
}
Poznámka:
Typy TypeScriptu pomáhají definovat typy dat, které funkce používá. I když nepotřebujete TypeScript k použití rozhraní Jest nebo jiných testovacích architektur JavaScriptu, je nezbytné pro psaní type-safe JavaScriptu.
Funkce v této aplikaci jsou:
Funkce | Popis |
---|---|
insertDocument | Vloží dokument do databáze. To je to, co chceme otestovat. |
InputVerified | Ověřuje vstupní data proti schématu. Zajišťuje, aby data byla ve správném formátu (například platné e-mailové adresy, správně formátované adresy URL). |
cosmos.items.create | Funkce sady SDK pro službu Azure Cosmos DB s využitím @azure/cosmos To je to, co chceme napodobenit. Už má své vlastní testy, které udržují vlastníci balíčků. Potřebujeme ověřit, že volání funkce Cosmos DB bylo provedeno a vráceno data, pokud příchozí data prošla ověřením. |
Instalace závislosti testovací architektury
Tato architektura je poskytována jako součást Node.js LTS.
Konfigurace balíčku pro spuštění testu
package.json
Aktualizujte aplikaci pomocí nového skriptu, který otestuje naše soubory zdrojového kódu. Soubory zdrojového kódu jsou definovány shodou s částečným názvem a příponou souboru. Test Runner hledá soubory podle běžných zásad vytváření názvů pro testovací soubory: <file-name>.spec.[jt]s
. Tento vzor znamená, že soubory pojmenované jako následující příklady se interpretují jako testovací soubory a spouští nástroj Test Runner:
- * .test.js: Například math.test.js
- * .spec.js: Například math.spec.js
- Soubory umístěné v adresáři testů, jako jsou testy nebomath.js
Přidejte do package.json skript, který podporuje tento vzor testovacího souboru pomocí nástroje Test Runner:
"scripts": {
"test": "node --test --experimental-test-coverage --experimental-test-module-mocks --trace-exit"
}
Nastavení jednotkového testu pro Azure SDK
Jak můžeme použít mocky, stuby a fejky pro testování funkce insertDocument?
- Napodobení: Potřebujeme napodobení, abychom se ujistili, že chování funkce je testováno, například:
- Pokud data projdou ověřením, volání funkce Cosmos DB proběhlo pouze 1krát
- Pokud data nepřejdou ověřením, volání funkce Cosmos DB se nestalo.
- Zápatí
- Předaná data odpovídají novému dokumentu vráceného funkcí.
Při testování se zamyslete nad nastavením testu, samotným testem a ověřením. Z hlediska testovacího jazyka tato funkce používá následující termíny:
- Uspořádání: nastavení testovacích podmínek
- Akt: zavolejte svou funkci k testování, označovanou také jako systém pod testem nebo SUT
- Assert: ověřte výsledky. Výsledky můžou být chování nebo stav.
- Chování značí funkčnost testovací funkce, kterou lze ověřit. Jedním z příkladů je volání určité závislosti.
- Stav označuje data vrácená funkcí.
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
});
});
Když v testech použijete napodobení, musí tento kód šablony použít napodobení k otestování funkce bez volání základní závislosti použité ve funkci, jako jsou klientské knihovny Azure.
Vytvoření testovacího souboru
Testovací soubor s mocky, k simulaci volání závislosti, obsahuje dodatečné nastavení.
Testovací soubor obsahuje několik částí:
-
import
: Příkazy importu umožňují použít nebo napodobit jakýkoli váš test. -
mock
: Vytvořte výchozí simulované chování, které požadujete. Každý test se může podle potřeby změnit. -
describe
: Testovací skupina proinsert.ts
soubor. -
it
: Každý test souboruinsert.ts
.
Testovací soubor zahrnuje tři testy pro insert.ts
soubor, které lze rozdělit do dvou typů ověřování:
Typ ověření | Zkouška |
---|---|
Šťastná cesta: should insert document successfully |
Mockovaná metoda databáze byla volána a vrátila změněná data. |
Cesta k chybě: should return verification error if input is not verified |
Ověření dat se nezdařilo a vrátila chybu. |
Chybná cesta:should return error if db insert fails |
Volala se napodobená metoda databáze a vrátila chybu. |
Následující testovací soubor ukazuje, jak otestovat funkci 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,
});
});
});
Řešení problémů
Většina kódu v tomto článku pochází z úložiště MicrosoftDocs/node-essentials Na GitHubu. Pokud chcete vložit do cloudového prostředku Cosmos DB, vytvořte ho pomocí tohoto skriptu.