Megosztás a következőn keresztül:


Azure SDK-integráció tesztelése JavaScript-alkalmazásokban

A JavaScripthez készült Azure SDK integrációs kódjának tesztelése elengedhetetlen ahhoz, hogy az alkalmazások megfelelően működjenek együtt az Azure-szolgáltatásokkal. Ez az útmutató bemutatja, hogyan tesztelheti hatékonyan az Azure SDK-integrációt a JavaScript-alkalmazásokban egy tesztelési keretrendszerben.

Amikor eldönti, hogy ki szeretné-e másolni a felhőszolgáltatás SDK-hívásait, vagy egy élő szolgáltatást használ tesztelési célokra, fontos figyelembe venni a sebesség, a megbízhatóság és a költségek közötti kompromisszumot. Ez a cikk bemutatja, hogyan használható tesztelési keretrendszer az SDK-integráció teszteléséhez. Az alkalmazáskód beszúr egy dokumentumot a Cosmos DB-be. A tesztkód kiszűkiti az erőforrás-használatot, hogy a felhőbeli erőforrás ne legyen használatban.

A használt keretrendszerek a következők:

  • Jest és CommonJs
  • Vitest és ESM
  • Node.js ESM-et használó tesztfuttató

Előfeltételek

Node.js LTS-t. Az LTS kiadási állapota "hosszú távú támogatás", amely általában garantálja, hogy a kritikus hibákat összesen 30 hónapig kijavítjuk.

A Node.js tesztfuttató a Node.js telepítésének része.

Figyelmeztetés

A Node.js tesztfuttatóhoz megadott minta a kísérleti node:test modult használja, a mock.fn() függvény alkalmazásával. Ne feledje, hogy a Node beépített tesztfuttatója még nem kínál teljes mértékben támogatott szimulálási API-t. Győződjön meg arról, hogy a cél Node verzió támogatja-e a kísérleti API-kat, vagy fontolja meg egy külső gyártótól származó mock könyvtár (vagy stub függvények) használatát.

Felhőszolgáltatások szimulálása

Előnyök:

  • Felgyorsítja a tesztcsomagot a hálózati késés megszüntetésével.
  • Kiszámítható és szabályozott tesztkörnyezeteket biztosít.
  • Egyszerűbben szimulálhat különböző forgatókönyveket és peremes eseteket.
  • Csökkenti az élő felhőszolgáltatások használatával kapcsolatos költségeket, különösen a folyamatos integrációs folyamatokban.

hátrányok:

  • A makettek a tényleges SDK-ról eltávolodhatnak, ami eltérésekhez vezethet.
  • Figyelmen kívül hagyhatja az élő szolgáltatás bizonyos funkcióit vagy viselkedését.
  • Kevésbé reális környezet az éles környezethez képest.

Élő szolgáltatás használata

Előnyök:

  • Valósághű környezet, amely szorosan tükrözi a termelést?
  • Hasznos az integrációs teszteknél, hogy a rendszer különböző részei működjenek együtt?
  • Hasznos a hálózat megbízhatóságával, a szolgáltatás rendelkezésre állásával és a tényleges adatkezeléssel kapcsolatos problémák azonosítása?

hátrányok:

  • A hálózati hívások miatt lassabb.
  • A lehetséges szolgáltatáshasználati költségek miatt drágább.
  • Összetett és időigényes az éles környezetnek megfelelő élő szolgáltatási környezet beállítása és karbantartása.

A mockolás és az élő szolgáltatások használata közötti választás a tesztelési stratégiától függ. Az olyan egységtesztek esetében, ahol a sebesség és a vezérlés a legfontosabb, gyakran a gúnyolás a jobb választás. Az olyan integrációs tesztek esetében, ahol a realizmus kulcsfontosságú, az élő szolgáltatás használata pontosabb eredményeket biztosíthat. E megközelítések kiegyensúlyozása segít átfogó tesztelési lefedettséget elérni a költségek kezelése és a teszthatékonyság fenntartása mellett.

Tesztpárok: Makettek, csonkok és hamisak

A teszthelyettesítő bármilyen fajta helyettesítő, amit valós dolgok helyett használnak tesztelési célokra. A választott dupla típus attól függ, hogy mit szeretne lecserélni. A mock kifejezést gyakran kettősként értik, ha a kifejezést alkalmi használatban használják. Ebben a cikkben a kifejezést kifejezetten a Jest tesztelési keretrendszerben használjuk és szemléltetik.

Utánzatok

Mocks (más néven kémek): Helyettesítse a függvényt, és képes legyen ellenőrizni és kémkedni a függvény viselkedését , amikor közvetetten más kód hívja meg.

Az alábbi példákban 2 függvényt használhat:

  • someTestFunction: A tesztelni kívánt függvény. Egy függőséget hív meg, dependencyFunction, amelyet nem Ön írt, és nem kell tesztelnie.
  • dependencyFunctionMock: A függőség modellje.
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);

A teszt célja annak biztosítása, hogy egyesTestFunction megfelelően viselkedjen a függőségi kód tényleges invokálása nélkül. A teszt ellenőrzi, hogy a rendszer meghívta-e a függőség mintapéldányát.

Nagy és kis függőségek szimulálása

Ha úgy dönt, hogy egy függőséget modellez, választhatja, hogy a következőhöz hasonló módon modellezi a kívánt elemet:

  • Egy vagy két függvény nagyobb függőségből. A Jest részleges álobjektumokat kínál erre a célra.
  • Egy kisebb függőség összes függvénye, ahogyan az ebben a cikkben látható példában is látható.

Vázlatok

A csonk célja, hogy egy függvény visszatérési adatait lecserélje, ezzel különböző forgatókönyveket szimuláljon. Egy csonkfüggvény használata lehetőséget ad a kódnak a függvény meghívására, és különböző állapotok fogadására, beleértve a sikeres eredményeket, a hibákat, a kivételeket és a szélsőséges eseteket. Az állapotellenőrzés biztosítja, hogy a kód megfelelően kezelje ezeket a forgatókönyveket.

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}`);

Az előző teszt célja annak biztosítása, hogy az elvégzett someTestFunction munka megfeleljen a várt eredménynek. Ebben az egyszerű példában a függvény feladata az utó- és családnevek összefűzése. Hamis adatok használatával ismeri a várt eredményt, és ellenőrizheti, hogy a függvény megfelelően végzi-e a munkát.

Hamisítványok

A helyettesítők olyan funkciókat kínálnak, amelyeket általában nem használna éles környezetben, például memóriabeli adatbázis alkalmazása a felhőbeli adatbázis helyett.

// 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]);
  });
});

Az előző teszt célja annak biztosítása, hogy someTestFunction megfelelően működjön együtt az adatbázissal. Hamis memóriabeli adatbázis használatával anélkül tesztelheti a függvény logikáját, hogy valódi adatbázisra támaszkodik, így a tesztek gyorsabbak és megbízhatóbbak lesznek.

Forgatókönyv: Dokumentum beszúrása a Cosmos DB-be az Azure SDK használatával

Tegyük fel, hogy rendelkezik egy alkalmazással, amely új dokumentumot kell írnia a Cosmos DB-be , ha az összes információ elküldve és ellenőrizve van. Ha üres űrlapot küld el, vagy az információ nem felel meg a várt formátumnak, az alkalmazásnak nem szabad megadnia az adatokat.

Példaként a Cosmos DB-t használják, de a fogalmak a JavaScripthez készült Azure SDK-k többségére vonatkoznak. A következő függvény rögzíti ezt a funkciót:

// 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;
    }
  }
}

Megjegyzés:

A TypeScript-típusok segítenek meghatározni a függvény által használt adattípusokat. Bár nincs szükség TypeScriptre a Jest vagy más JavaScript tesztelési keretrendszerek használatához, ez elengedhetetlen a típusbiztos JavaScript írásához.

Az alkalmazás funkciói a következők:

Funkció Leírás
insertDocument Dokumentum beszúrása az adatbázisba. Ezt szeretnénk tesztelni.
inputVerified Ellenőrzi a bemeneti adatokat egy sémán. Biztosítja, hogy az adatok a megfelelő formátumban (például érvényes e-mail-címek, helyesen formázott URL-címek) jelenjenek meg.
cosmos.items.create Az Azure Cosmos DB SDK-függvénye a @azure/cosmos használatával. Ezt szeretnénk kiszúnyolni. Már rendelkezik saját tesztekkel, amelyeket a csomagtulajdonosok tartanak fenn. Ellenőriznünk kell, hogy a Cosmos DB-függvény hívása megtörtént-e, és adatokat ad-e vissza, ha a bejövő adatok ellenőrzésen mentek át.

Tesztelési keretrendszer függőségének telepítése

Ez a keretrendszer Node.js LTS részeként érhető el.

Csomag konfigurálása teszt futtatásához

Frissítse az package.json alkalmazást egy új szkripttel a forráskódfájlok teszteléséhez. A forráskódfájlok a részleges fájlnév és -bővítmény egyezésével vannak definiálva. A tesztfuttató a tesztfájlok általános elnevezési konvenciója szerint keres fájlokat: <file-name>.spec.[jt]s. Ez a minta azt jelenti, hogy a következő példákhoz hasonló fájlok tesztfájlokként vannak értelmezve, és a tesztfuttató futtatja:

  • * .test.js: Például math.test.js
  • * .spec.js: Például math.spec.js
  • Egy tesztkönyvtárban található fájlok, például tesztek/math.js

Adjon hozzá egy szkriptet a package.json-hoz, amely támogatja a tesztfájlmintát a tesztfuttatóval.

"scripts": {
    "test": "node --test --experimental-test-coverage --experimental-test-module-mocks --trace-exit"
}

Egységteszt beállítása az Azure SDK-hoz

Hogyan használhatunk maketteket, csonkokat és hamis elemeket az insertDocument függvény teszteléséhez?

  • Mocks: szükségünk van egy makettre a függvény viselkedésének teszteléséhez, például:
    • Ha az adatok nem adják át az ellenőrzést, a Cosmos DB függvény hívása csak 1 alkalommal történt
    • Ha az adatok nem adják át az ellenőrzést, a Cosmos DB függvény hívása nem történt meg
  • Csonkokat:
    • A megadott adatok megegyeznek a függvény által visszaadott új dokumentumtal.

A tesztelés során gondolja át a tesztbeállítást, magát a tesztet és az ellenőrzést. A teszt nyelvezet szempontjából ez a funkció a következő kifejezéseket használja:

  • Elrendezés: a tesztfeltételek beállítása
  • Művelet: a függvény meghívása tesztelésre, más néven a tesztelés alatt álló rendszer vagy a SUT
  • Igazolja: ellenőrizze az eredményeket. Az eredmények lehetnek viselkedés vagy állapot.
    • A viselkedés a tesztfüggvény funkcióját jelzi, amely ellenőrizhető. Az egyik példa az, hogy néhány függőséget meghívtak.
    • Az állapot a függvényből visszaadott adatokat jelzi.
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
  });
});

Amikor a tesztekben mock objektumokat használ, a sablonkódnak a függvény teszteléséhez a függvényben használt mögöttes függőségek, például az Azure ügyfelek könyvtárainak meghívása nélkül kell alkalmaznia a mockolást.

A tesztfájl létrehozása

A modellekkel ellátott tesztfájl, amely egy függőség hívását szimulálja, további beállítással rendelkezik.

A tesztfájlnak több része is van:

  • import: Az import utasítások lehetővé teszik a tesztek használatát vagy megcsúfolását.
  • mock: Hozza létre a kívánt alapértelmezett modell viselkedését. Minden teszt szükség szerint módosítható.
  • describe: Tesztcsoport család a insert.ts fájlhoz.
  • it: Minden teszt a(z) insert.ts fájlhoz.

A tesztfájl három tesztet tartalmaz a insert.ts fájlhoz, amelyek két érvényesítési típusra oszthatók:

Érvényesítési típus Teszt
Boldog út: should insert document successfully A mocked database metódus meghívása megtörtént, és visszaadta a módosított adatokat.
Hiba elérési útja: should return verification error if input is not verified Az adatok érvényesítése sikertelen volt, és hibát adott vissza.
A hiba útvonala:should return error if db insert fails A modellelt adatbázis metódust meghívták, és hibát adott vissza.

Az alábbi tesztfájl bemutatja, hogyan tesztelheti az insertDocument függvényt .

// 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,
    });
  });
});

Hibaelhárítás

A cikkben szereplő kód nagy része a MicrosoftDocs/node-essentials GitHub-adattárból származik. Ha be szeretne szúrni egy Cosmos DB Cloud-erőforrásba, hozza létre az erőforrást ezzel a szkripttel.

További információk