Sdílet prostřednictvím


Testování integrace sady Azure SDK v javascriptových aplikacích

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.

Při rozhodování o tom, jestli se mají napodobit volání sady SDK cloudové služby nebo použít živou službu pro účely testování, je důležité zvážit kompromisy mezi rychlostí, spolehlivostí a náklady.

Požadavky

Napodobování cloudových služeb

Profesionálové:

  • 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

Profesionálové:

  • Poskytuje realistické prostředí, které úzce zrcadlí produkční prostředí.
  • Užitečné pro integrační testy, které zajišťují spolupráci různých částí systému.
  • Pomáhá identifikovat problémy související se spolehlivostí sítě, dostupností služeb a skutečným zpracováním dat.

Nevýhody:

  • Pomalejší kvůli síťovým voláním.
  • Dražší kvůli potenciálním nákladům na využití služeb.
  • Složité a časově náročné na nastavení a údržbu živého prostředí služby, které odpovídá produkčnímu prostředí.

Volba mezi napodobování a používání živých služeb závisí na vaší testovací strategii. Pro testy jednotek, kde je rychlost a řízení nejdůležitější, je často lepší volbou napodobování. 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ů.

Test doubles: Napodobení, zástupné procedury a falešné

Testovací double je jakýkoli druh náhrady použité místo něčeho skutečného pro účely testování. Typ dvojitého výběru je založený 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

Napodobení (označované také jako špioni): Nahraďte funkci a být schopni řídit a špehovat chování této funkce, když je volána nepřímo nějakým jiným kódem.

V následujících příkladech máte 2 funkce:

  • someTestFunction: Jedná se o funkci, kterou potřebujete otestovat. Volá závislost, dependencyFunctionkterou jste nenapsali a nepotřebujete testovat.
  • dependencyFunctionMock: Jedná se o napodobení závislosti.
// setup
const dependencyFunctionMock = jest.fn();

// perform test
// Jest replaces the call to dependencyFunction with dependencyFunctionMock
const { name } = someTestFunction()

// verify behavior
expect(dependencyFunctionMock).toHaveBeenCalled();

Účelem testu je zajistit, aby se funkce someTestFunction chovala správně, aniž by skutečně vyvolávala kód závislosti. Test ověří, že se volal napodobení závislosti.

Napodobení velkých a malých závislostí

Když se rozhodnete napodobení závislosti, můžete se rozhodnout, že si napodobíte jenom to, co potřebujete, například:

Zástupné procedury

Účelem zástupných procedur je nahradit návratová data funkce pro simulaci různých scénářů. Díky tomu může kód volat funkci a přijímat různé stavy, 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ě.

// ARRANGE
const dependencyFunctionMock = jest.fn();
const fakeDatabaseData = {first: 'John', last: 'Jones'};
dependencyFunctionMock.mockReturnValue(fakeDatabaseData);

// ACT
// date is returned by mock then transformed in SomeTestFunction()
const { name } = someTestFunction()

// ASSERT
expect(name).toBe(`${first} ${last}`);

Účelem předchozího testu je zajistit, aby práce provedená someTestFunction splněním očekávaného výsledku. V tomto jednoduchém příkladu je úkolem funkce zřetězení křestní jména 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
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);
}

// Jest test suite
describe('someTestFunction', () => {
  let fakeDb: FakeDatabase;
  let testKey: string;
  let testValue: any;

  beforeEach(() => {
    fakeDb = new FakeDatabase();
    testKey = 'testKey';
    testValue = {
      first: 'John',
      last: 'Jones',
      lastUpdated: new Date().toISOString(),
    };

    // Spy on the save method
    jest.spyOn(fakeDb, 'save');
  });

  afterEach(() => {
    // Clear all mocks
    jest.clearAllMocks();
  });

  test('should save and return the correct value', () => {
    // Perform test
    const result = someTestFunction(fakeDb, testKey, testValue);

    // Verify state
    expect(result).toEqual(testValue);
    expect(result.first).toBe('John');
    expect(result.last).toBe('Jones');
    expect(result.lastUpdated).toBe(testValue.lastUpdated);

    // Verify behavior
    expect(fakeDb.save).toHaveBeenCalledWith(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 cosmos DB pomocí sady Azure SDK

Představte si, že máte aplikaci, která potřebuje napsat nový dokument do služby Cosmos DB , pokud se všechny informace odesílají a ověří. 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';
import {
  DbDocument,
  DbError,
  RawInput,
  VerificationErrors,
} from '../data/model';
import { inputVerified } from '../data/verify';

export async function insertDocument(
  container: Container,
  doc: RawInput,
): Promise<DbDocument | DbError | VerificationErrors> {
  const isVerified: boolean = 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 používat Rozhraní Jest nebo jiné testovací architektury JavaScriptu, je nezbytné pro psaní type-safe JavaScriptu.

Výše uvedené funkce v této aplikaci jsou:

Function 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. Vlastní testy, které vlastní vlastníci balíčků udržují, už má. 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

Tento článek používá Jako testovací architekturu Jest . Existují i další testovací architektury, které jsou srovnatelné s používáním.

V kořenovém adresáři aplikace nainstalujte Jest následujícím příkazem:

npm install jest

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. Jest 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 spustí jest:

  • *.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 nebo math.js

Přidejte do package.json skript, který podporuje tento vzor testovacího souboru pomocí Jestu:

"scripts": {
    "test": "jest dist",
}

Zdrojový kód TypeScriptu dist se vygeneruje do podsložky. Jest spustí .spec.js soubory nalezené v dist podsložce.

Nastavení testu jednotek pro Sadu Azure SDK

Jak můžeme použít napodobení, zástupné procedury a falešné kódy k otestová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.
  • Naráží:
    • 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 vernakulárního se to označuje jako:

  • Uspořádání: nastavení testovacích podmínek
  • Act: volání funkce k otestování, označované 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í.

Jest, podobně jako u jiných testovacích architektur, má testovací soubor často používaný k definování testovacího souboru.

// boilerplate.spec.ts

describe('nameOfGroupOfTests', () => {
  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
  });
});

Pokud používáte napodobení, musí toto kotlové místo 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 napodobenými napodobení volání závislosti má další nastavení navíc k běžnému často používanému testovacímu kódu. Níže uvedený testovací soubor obsahuje několik částí:

  • import: Příkazy importu umožňují použít nebo napodobení jakéhokoli testu.
  • jest.mock: Vytvořte požadované výchozí chování napodobení. Každý test se může podle potřeby změnit.
  • describe: Testovací skupina pro insert.ts soubor.
  • test: Každý test souboru insert.ts .

Testovací soubor zahrnuje tři testy pro insert.ts soubor, které lze rozdělit do dvou typů ověřování:

Typ ověření Test
Šťastná cesta: should insert document successfully Volala se napodobená metoda databáze 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.
Cesta k chybě:should return error if db insert fails Volala se napodobená metoda databáze a vrátila chybu.

Následující testovací soubor Jest ukazuje, jak otestovat funkci insertDocument .

// insertDocument.test.ts
import { Container } from '../data/connect-to-cosmos';
import { createTestInputAndResult } from '../data/fake-data';
import {
  DbDocument,
  DbError,
  isDbError,
  isVerificationErrors,
  RawInput,
} from '../data/model';
import { inputVerified } from '../data/verify';
import { insertDocument } from './insert';

// Mock app dependencies for Cosmos DB setup
jest.mock('../data/connect-to-cosmos', () => ({
  connectToContainer: jest.fn(),
  getUniqueId: jest.fn().mockReturnValue('unique-id'),
}));

// Mock app dependencies for input verification
jest.mock('../data/verify', () => ({
  inputVerified: jest.fn(),
}));

describe('insertDocument', () => {
  // Mock the Cosmo DB Container object
  let mockContainer: jest.Mocked<Container>;

  beforeEach(() => {
    // Clear all mocks before each test
    jest.clearAllMocks();

    // Mock the Cosmos DB Container create method
    mockContainer = {
      items: {
        create: jest.fn(),
      },
    } as unknown as jest.Mocked<Container>;
  });

  it('should return verification error if input is not verified', async () => {
    // Arrange - Mock the input verification function to return false
    jest.mocked(inputVerified).mockReturnValue(false);

    // Arrange - wrong shape of doc on purpose
    const doc = { name: 'test' };

    // Act - Call the function to test
    const insertDocumentResult = await insertDocument(
      mockContainer,
      doc as unknown as RawInput,
    );

    // Assert - State verification: Check the result when verification fails
    if (isVerificationErrors(insertDocumentResult)) {
      expect(insertDocumentResult).toEqual({
        message: 'Verification failed',
      });
    } else {
      throw new Error('Result is not of type VerificationErrors');
    }

    // Assert - Behavior verification: Ensure create method was not called
    expect(mockContainer.items.create).not.toHaveBeenCalled();
  });

  it('should insert document successfully', async () => {
    // Arrange - create input and expected result data
    const { input, result }: { input: RawInput; result: Partial<DbDocument> } =
      createTestInputAndResult();

    // Arrange - mock the input verification function to return true
    (inputVerified as jest.Mock).mockReturnValue(true);
    (mockContainer.items.create as jest.Mock).mockResolvedValue({
      resource: result,
    });

    // Act - Call the function to test
    const insertDocumentResult = await insertDocument(mockContainer, input);

    // Assert - State verification: Check the result when insertion is successful
    expect(insertDocumentResult).toEqual(result);

    // Assert - Behavior verification: Ensure create method was called with correct arguments
    expect(mockContainer.items.create).toHaveBeenCalledTimes(1);
    expect(mockContainer.items.create).toHaveBeenCalledWith({
      id: input.id,
      name: result.name,
    });
  });

  it('should return error if db insert fails', async () => {
    // Arrange - create input and expected result data
    const { input, result } = createTestInputAndResult();

    // Arrange - mock the input verification function to return true
    jest.mocked(inputVerified).mockReturnValue(true);

    // Arrange - mock the Cosmos DB create method to throw an error
    const mockError: DbError = {
      message: 'An unknown error occurred',
      code: 500,
    };
    jest.mocked(mockContainer.items.create).mockRejectedValue(mockError);

    // Act - Call the function to test
    const insertDocumentResult = await insertDocument(mockContainer, input);

    // Assert - verify type as DbError
    if (isDbError(insertDocumentResult)) {
      expect(insertDocumentResult.message).toBe(mockError.message);
    } else {
      throw new Error('Result is not of type DbError');
    }

    // Assert - Behavior verification: Ensure create method was called with correct arguments
    expect(mockContainer.items.create).toHaveBeenCalledTimes(1);
    expect(mockContainer.items.create).toHaveBeenCalledWith({
      id: input.id,
      name: result.name,
    });
  });
});

Další informace