Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Testowanie kodu integracji dla zestawu Azure SDK dla języka JavaScript jest niezbędne do zapewnienia prawidłowej interakcji aplikacji z usługami platformy Azure. W tym przewodniku pokazano, jak skutecznie przetestować integrację zestawu Azure SDK w twoich aplikacjach JavaScript przy użyciu frameworka testowego.
Podejmując decyzję, czy zamockować wywołania SDK usług w chmurze, czy używać usługi na żywo do celów testowych, ważne jest, aby wziąć pod uwagę kompromis pomiędzy szybkością, niezawodnością a kosztami. W tym artykule pokazano, jak używać platformy testowej do testowania integracji zestawu SDK. Kod aplikacji wstawia dokument do usługi Cosmos DB. Kod testowy symuluje użycie zasobów, aby zasób w chmurze nie był używany.
Używane struktury to:
- Jest z usługą CommonJs
- Vitest z usługą ESM
- Node.js Moduł uruchamiający testy z wykorzystaniem ESM
Wymagania wstępne
Node.js LTS. Stan wydania LTS to "długoterminowa obsługa", która zwykle gwarantuje, że krytyczne usterki zostaną naprawione przez łącznie 30 miesięcy.
Moduł uruchamiający testyNode.js jest częścią instalacji Node.js.
Ostrożność
Przykład podany dla uruchamiacza testów Node.js używa eksperymentalnego modułu node:test wraz z funkcją mock.fn(). Pamiętaj, że wbudowany moduł uruchamiający testy platformy Node nie oferuje jeszcze w pełni obsługiwanego interfejsu API pozorowania. Upewnij się, że wersja Node, którą używasz, obsługuje eksperymentalne interfejsy API, lub rozważ użycie biblioteki do pozorowania innej firmy (lub funkcji podróbek).
Zasymulowanie usług w chmurze
Zalety:
- Przyspiesza zestaw testów, eliminując opóźnienie sieci.
- Zapewnia przewidywalne i kontrolowane środowiska testowe.
- Łatwiej symulować różne scenariusze i przypadki brzegowe.
- Zmniejsza koszty związane z używaniem usług chmurowych, zwłaszcza w procesach ciągłej integracji.
Słabe strony:
- Makiety mogą odbiegać od rzeczywistego zestawu SDK, co prowadzi do rozbieżności.
- Może ignorować niektóre funkcje lub zachowania usługi na żywo.
- Mniej realistyczne środowisko w porównaniu z produkcją.
Korzystanie z usługi na żywo
Zalety:
- Czy istnieje realistyczne środowisko, które ściśle odzwierciedla produkcję?
- Czy testy integracji są przydatne w celu zapewnienia współpracy różnych części systemu?
- Czy pomocne jest zidentyfikowanie problemów związanych z niezawodnością sieci, dostępnością usługi i rzeczywistą obsługą danych?
Słabe strony:
- Jest wolniejsza z powodu wywołań sieciowych.
- Jest droższa ze względu na potencjalne koszty użycia usługi.
- Skonfigurowanie i utrzymanie środowiska usług na żywo zgodnego z produkcją jest skomplikowane i czasochłonne.
Wybór między wyśmiewaniem i używaniem usług na żywo zależy od strategii testowania. W przypadku testów jednostkowych, w których szybkość i kontrola są najważniejsze, pozorowanie jest często lepszym wyborem. W przypadku testów integracji, w których realizm ma kluczowe znaczenie, użycie usługi na żywo może zapewnić dokładniejsze wyniki. Równoważenie tych podejść pomaga osiągnąć kompleksowy zakres testów podczas zarządzania kosztami i utrzymania wydajności testów.
Dublety testowe: Makiety, wycinki i podróbki
Test podwójny to jakikolwiek zamiennik używany zamiast czegoś rzeczywistego do celów testowych. Typ podwójnej kopii, który wybierasz, zależy od tego, co chcesz zastąpić. Termin mock jest często rozumiany jako każdy zamiennik , gdy jest używany w sposób potoczny. W tym artykule termin jest używany specjalnie i zilustrowany specjalnie w strukturze testowej Jest.
Mocki
Makiety (nazywane również szpiegami): Zastępują funkcję, umożliwiając kontrolowanie i monitorowanie zachowania tej funkcji, gdy jest wywoływana pośrednio przez inny kod.
W poniższych przykładach masz 2 funkcje:
-
someTestFunction: funkcja, którą należy przetestować. Wywołuje zależność
dependencyFunction, której nie napisałeś i nie musisz testować. - dependencyFunctionMock: jest mockiem zależności.
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);
Celem testu jest upewnienie się, że funkcja someTestFunction działa prawidłowo bez wywoływania kodu zależności. Test sprawdza, czy wywołano pozorowanie zależności.
Symulowanie dużych i małych zależności
Kiedy zdecydujesz się zamockować zależność, możesz zamockować tylko te elementy, których potrzebujesz, takie jak:
- Funkcja lub dwie z większej zależności. Jest oferuje częściowe mocki do tego celu.
- Wszystkie funkcje mniejszej zależności, jak pokazano w przykładzie w tym artykule.
Szkielety
Celem stubu jest zastąpienie zwracanych danych funkcji, aby symulować różne scenariusze. Możesz użyć wycinku, aby umożliwić kodowi wywoływanie funkcji i odbieranie różnych stanów, w tym pomyślnych wyników, niepowodzeń, wyjątków i przypadków brzegowych. Weryfikacja stanu gwarantuje, że kod obsługuje te scenariusze poprawnie.
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}`);
Celem powyższego testu jest zapewnienie, że praca wykonana przez someTestFunction program spełnia oczekiwany wynik. W tym prostym przykładzie zadaniem funkcji jest połączenie imienia i nazwiska. Korzystając z fałszywych danych, znasz oczekiwany wynik i możesz sprawdzić, czy funkcja wykonuje pracę prawidłowo.
Podróbki
Podróbki zastępują funkcjonalność, której zwykle nie używa się w środowisku produkcyjnym, takich jak używanie bazy danych w pamięci zamiast bazy danych w chmurze.
// 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]);
});
});
Celem powyższego testu jest zapewnienie prawidłowej someTestFunction interakcji z bazą danych. Korzystając z fałszywej bazy danych w pamięci, można przetestować logikę funkcji bez polegania na rzeczywistej bazie danych, dzięki czemu testy będą szybsze i bardziej niezawodne.
Scenariusz: Wstawianie dokumentu do usługi Cosmos DB przy użyciu zestawu Azure SDK
Załóżmy, że masz aplikację, która musi napisać nowy dokument w usłudze Cosmos DB , jeśli wszystkie informacje zostały złożone i zweryfikowane. Jeśli zostanie przesłany pusty formularz lub informacje nie są zgodne z oczekiwanym formatem, aplikacja nie powinna wprowadzać danych.
Usługa Cosmos DB jest używana jako przykład, jednak koncepcje dotyczą większości zestawów SDK platformy Azure dla języka JavaScript. Poniższa funkcja przechwytuje tę funkcję:
// 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;
}
}
}
Uwaga
Typy TypeScript ułatwiają definiowanie rodzajów danych używanych przez funkcję. Chociaż nie musisz korzystać z TypeScript do używania Jest lub innych frameworków testowania JavaScript, jest on niezbędny do pisania JavaScript z bezpieczeństwem typów.
Funkcje w tej aplikacji to:
| Funkcja | opis |
|---|---|
| insertDocument | Wstawia dokument do bazy danych. To jest to, co chcemy przetestować. |
| inputVerified | Weryfikuje dane wejściowe względem schematu. Zapewnia, że dane są w poprawnym formacie (na przykład prawidłowe adresy e-mail, poprawnie sformatowane adresy URL). |
| cosmos.items.create | Funkcja zestawu SDK dla usługi Azure Cosmos DB korzystająca z @azure/cosmos. To jest to, co chcemy wyśmiewać. Ma już własne testy utrzymywane przez właścicieli pakietów. Musimy sprawdzić, czy wywołanie funkcji usługi Cosmos DB zostało wykonane i zwróciło dane, jeśli dane przychodzące przeszły weryfikację. |
Instalowanie zależności platformy testowej
Ta struktura jest udostępniana w ramach Node.js LTS.
Konfigurowanie pakietu do uruchamiania testu
package.json Zaktualizuj aplikację za pomocą nowego skryptu, aby przetestować nasze pliki kodu źródłowego. Pliki kodu źródłowego są identyfikowane poprzez dopasowanie częściowej nazwy pliku i rozszerzenia. Moduł uruchamiający testy wyszukuje pliki zgodnie ze wspólną konwencją nazewnictwa dla plików testowych: <file-name>.spec.[jt]s. Ten wzorzec oznacza, że pliki o nazwie podobnej do następujących przykładów są interpretowane jako pliki testowe i uruchamiane przez moduł uruchamiający testy:
- * .test.js: na przykład math.test.js
- * .spec.js: np. math.spec.js
- Pliki znajdujące się w katalogu testów, takie jak testy/math.js
Dodaj skrypt do package.json , aby obsługiwać ten wzorzec pliku testowego za pomocą modułu uruchamiającego testy:
"scripts": {
"test": "node --test --experimental-test-coverage --experimental-test-module-mocks --trace-exit"
}
Konfigurowanie testu jednostkowego dla zestawu Azure SDK
Jak możemy używać makiety, wycinków i podróbek do testowania funkcji insertDocument ?
- Makiety: potrzebujemy makiety, aby upewnić się, że zachowanie funkcji jest testowane, takie jak:
- Jeśli dane przechodzą weryfikację, wywołanie funkcji Cosmos DB miało miejsce tylko 1 raz
- Jeśli dane nie przechodzą weryfikacji, wywołanie funkcji Cosmos DB nie powiodło się
- Wycinki:
- Przekazane dane są zgodne z nowym dokumentem zwracanym przez funkcję.
Podczas testowania zastanów się nad konfiguracją testu, samym testem i weryfikacją. Jeśli chodzi o język testowy, ta funkcja używa następujących terminów:
- Rozmieszczanie: konfigurowanie warunków testowych
- Działanie: wywołaj funkcję w celu przetestowania, znaną również jako system testowany lub SUT
- Potwierdzenie: zweryfikuj wyniki. Wyniki mogą być związane z zachowaniem lub stanem.
- Zachowanie wskazuje funkcjonalność w funkcji testowej, którą można zweryfikować. Przykładem jest wywołanie pewnej zależności.
- Stan wskazuje zwracane dane z funkcji.
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
});
});
Jeśli używasz makiety w testach, kod szablonu musi używać pozorowania do testowania funkcji bez wywoływania podstawowej zależności używanej w funkcji, takiej jak biblioteki klienta platformy Azure.
Tworzenie pliku testowego
Plik testowy z fałszywkami, służącymi do symulacji wywołania zależności, ma dodatkowe ustawienia konfiguracyjne.
Plik testowy zawiera kilka części:
-
import: Instrukcje importowania umożliwiają wykorzystanie lub mockowanie dowolnego testu. -
mock: Utwórz domyślne pozorne zachowanie. Każdy test może zmienić się zgodnie z potrzebami. -
describe: Rodzina grup testowych dlainsert.tspliku. -
it: każdy test plikuinsert.ts.
Plik testowy obejmuje trzy testy dla insert.ts pliku, które można podzielić na dwa typy weryfikacji:
| Typ weryfikacji | Testowanie |
|---|---|
Szczęśliwa ścieżka: should insert document successfully |
Wywołano zasymulowaną metodę bazy danych, która zwróciła zmienione dane. |
Ścieżka błędu: should return verification error if input is not verified |
Sprawdzanie poprawności danych nie powiodło się i zwróciło błąd. |
Ścieżka błędu:should return error if db insert fails |
Wywołano wyśmiewaną metodę bazy danych i zwrócono błąd. |
Poniższy plik testowy pokazuje, jak przetestować funkcję 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,
});
});
});
Rozwiązywanie problemów
Większość kodu w tym artykule pochodzi z repozytorium GitHub MicrosoftDocs/node-essentials . Jeśli chcesz wstawić do zasobu usługi Cosmos DB Cloud, utwórz zasób za pomocą tego skryptu.