Blog

Testowanie kompatybilności wstecznej serwisów API

Testowanie kompatybilności wstecznej serwisów API

Testowanie kompatybilności wstecznej API, znane również jako breaking changes testing, to proces sprawdzania, czy wprowadzenie zmian w interfejsie programistycznym (API) nie powoduje przerwania lub negatywnego wpływu na już istniejące aplikacje, systemy lub klientów korzystających z tego API. Breaking changes, czyli zmiany, które mogą zakłócić istniejącą funkcjonalność lub powodować problemy u użytkowników, są kluczowym obszarem testowania w kontekście ewolucji i utrzymania API.

Przyjrzyjmy się kilku aspektom testowania kompatybilności wstecznej API:

  • Zmiany w strukturze danych: Wprowadzenie zmian w strukturze danych, takie jak dodawanie, usuwanie lub modyfikowanie pól, może mieć wpływ na sposób, w jaki klient odbiera dane. Testowanie powinno sprawdzić, czy istniejące aplikacje nadal mogą poprawnie interpretować nową strukturę danych.

  • Modyfikacje istniejących zasobów: Zmiany w istniejących endpointach API, takie jak zmiana sposobu działania lub parametrów zapytania, mogą wpłynąć na już istniejące integracje. Testy powinny zweryfikować, czy te zmiany nie powodują błędów u klientów.

  • Usunięcie funkcji: Jeśli pewne funkcje zostaną usunięte z API, testowanie powinno sprawdzić, czy istniejące aplikacje są świadome tych zmian i nie próbują korzystać z usuniętych funkcji.

Wprowadzając zmiany w API, deweloperzy i zespoły odpowiedzialne za API muszą zadbać o to, aby błędy kompatybilności wstecznej były minimalizowane, odpowiednio zarządzane. Dobre praktyki programistyczne, takie jak stosowanie wersjonowania API, mogą pomóc w zarządzaniu tym procesem i ułatwieniu testowania kompatybilności wstecznej.

W dzisiejszym poście chciałbym przedstawić Wam narzędzia oraz framework, który pozwoli na skuteczne identyfikowanie i weryfikowanie zmian w specyfikacji API, mogących spowodować problemy z kompatybilnością wsteczną dla klientów korzystających z tego API.

Open API Diff

Open API Diff (openapi-diff) to narzędzie, które jest w stanie wykryć różne rodzaje zmian w specyfikacjach API, a w szczególności tzw. breaking changes, czyli zmiany, które mogą wpłynąć na istniejące implementacje klientów API i spowodować problemy z kompatybilnością wsteczną.

Narzędzie jest w stanie zidentyfikować, co zostało zmienione między dwoma plikami specyfikacji Swagger lub OpenAPI. Zmiany te są dzielone na trzy grupy: breaking (łamiące zgodność), non-breaking (niełamiące zgodności) i unclassified (niesklasyfikowane). Korzystając z podejścia opartego na teorii zbiorów, to narzędzie jest w stanie obliczyć te różnice z dużą dokładnością.

Klasyfikacja zmian wykrywanych przez Open API Diff

Zmiany wykryte przez narzędzie Open API Diff mogą byc przypisane do trzech grup:

Breaking (łamiące zgodność)

Zmiany w API, które powodują, iż klienci (consumers) korzystający z tego API działają w sposób niepoprawny.

  • Usunięcie ścieżki (Path) – Jeśli element, taki jak ścieżka (path), został usunięty w nowej wersji, może to prowadzić do problemów z klientami, którzy korzystają z tego elementu.

  • Usunięcie metody – Jeśli operacja (np. GET, POST) została usunięta, klientom może brakować oczekiwanej funkcjonalności.

  • Zmiana właściwości z opcjonalnej na wymaganą w ciele żądania – Jeśli właściwość była wcześniej opcjonalna, istniejące implementacje klientów mogą nie dostarczać tej właściwości w swoich zapytaniach, ponieważ była uznawana za nieobowiązkową.

  • Zmiana właściwości z wymaganej na opcjonalną w ciele odpowiedzi – Jeśli dana właściwość była wcześniej uznawana za wymaganą w odpowiedzi API, istniejące implementacje klientów mogą polegać na obecności tej właściwości.

Non Breaking (niełamiące zgodność)

Zmiany, które nie sprawią, że istniejący konsumenci staną się niekompatybilni z API, na przykład:

  • Dodanie ścieżki

  • Dodanie metody

  • Zmiana właściwości z wymaganej na opcjonalną w ciele żądania

  • Zmiana właściwości z opcjonalnej na wymaganą w ciele odpowiedz

Unclassified (niesklasyfikowane)

Zmiany, które zostały wykryte przez narzędzie, ale nie mogą zostać sklasyfikowane, na przykład:

  • Modyfikacje właściwości X-Properties

Open API Diff – Użycie z CLI

Na samym początku zainstaluj narzędzie za pomocą npm i dodaj je do pliku package.json:

npm install openapi-diff --save-dev

Następnie uruchom narzędzie, podając ścieżki do pliku źródłowego specyfikacji oraz pliku docelowego specyfikacji. Pliki te powinny być w formacie JSON lub YAML i być poprawnymi specyfikacjami Swagger 2 lub OpenAPI 3.

Narzędzie wygeneruje listę różnic łamiących zgodność, niełamiących zgodność oraz niesklasyfikowanych występujących między plikiem źródłowym a docelowym. Jeśli zostaną znalezione jakiekolwiek zmiany łamiące zgodność, narzędzie zwróci kod wyjścia różny od zera.

Jako przykładowe API do testów wykorzystamy serwis API: PetStore.

Zapiszmy teraz zawartość pliku swagger.json powyższego serwisu do dwóch osobnych plików o różnych nazwach np. swagger-groundtruth.json oraz swagger-actual.json

Uruchommy teraz następującą komendę z projektu, w którym zainstalowaliście bibliotekę openapi-diff:

.\node_modules\.bin\openapi-diff.cmd swagger-groundtruth.json swagger-actual.json

Jeśli nie macie ochoty tworzenia własnego projektu, możecie skorzystać z gotowego, przygotowanego na potrzeby tego tutoriala projektu znajdującego się na repozytorium GITHub Automatyzacja.IT. Projekt ten przyda się Wam również do uruchomienia testów opisanych w dalszej części tego wpisu.

Przed uruchomieniem komendy open-api-diff w CLI nie zapomnijcie zainstalować zależności projektu. W tym celu użyjcie komendy npm install.

Sama komenda uruchamiająca open-api-diff wygląda następująco:

.\node_modules\.bin\openapi-diff.cmd .\schemas\groundtruth\swagger.json .\schemas\actual\swagger.json

Z racji tego, iż oba pliki swagger.json mają tę sama zawartość, w terminalu powinniście otrzymać następującą odpowiedź: „No changes found between the two specifications”. Zalóżmy jednak, iż jeden z deweloperów usunął przypadkowo jedną z funkcji API Petstore. Przykładowo endpoint /user/logout:

    "/user/logout": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Logs out current logged in user session",
        "description": "",
        "operationId": "logoutUser",
        "produces": [
          "application/json",
          "application/xml"
        ],
        "parameters": [],
        "responses": {
          "default": {
            "description": "successful operation"
          }
        }
      }
    },

Usuńmy go teraz w całości z pliku swagger.json, który znajduję się w folderze schemas/actual, a następnie uruchommy ponownie komendę:

.\node_modules.bin\openapi-diff.cmd .\schemas\groundtruth\swagger.json .\schemas\actual\swagger.json

Tym razem open-api-diff wykrył potencjalny błąd kompatybilności wstecznej, ponieważ usunęliśmy ścieżkę, która mogła być używana przez klienta API:

Open API Diff – Użycie jako API w Node.js

Drugim sposobem użycia narzędzia open-api-diff jest skorzystanie z jego API w Node.js

Aby użyć bibliotekę open-api-diff, należy przekazać SpecOption dla specyfikacji źródłowej i docelowej. Warto nadmienić, iż SpecOption posiada trzy właściwości:

  • content (string) – ciąg znaków zawierający zserializowaną zawartość w formacie JSON lub YAML

  • location (string) – nazwa specyfikacji, zwykle ścieżka pliku lub nazwa pliku

  • format (string) – format specyfikacji – swagger2 albo openapi3

Poniżej znajduje się przykładowe użycie narzędzia jako API w Node.js:

const openapiDiff = require('openapi-diff');

const source = {
  // openapi3...
};

const destination = {
  // openapi3...
};

const result = await openapiDiff.diffSpecs({
  sourceSpec: {
    content: JSON.stringify(source),
    location: 'source.json',
    format: 'openapi3'
  },
  destinationSpec: {
    content: JSON.stringify(destination),
    location: 'destination.json',
    format: 'openapi3'
  }
});

if (result.breakingDifferencesFound) {
  console.log('Breaking change found!')
}

Open API Diff – Użycie narzędzia we frameworku

Przejdźmy teraz do przedstawienia autorskiego frameworka, wykorzystującego narzędzie open-api-diff. Tak jak już wcześniej wspomniałem, pełne rozwiązanie projektu możecie znaleźć na GITHub Automatyzacja.IT. Głównym zadaniem frameworka jest testowanie kompatybilności wstecznej API, w tym wypadku jednak do porównania zostanie użyta tkz. groundtruth schema (wzorcowa schema występująca zawsze w postaci pliku) oraz actual schema, która będzie zapisywana do pliku swagger.json w folderze schemas/actual po podaniu wskazanego URL’a danej specyfikacji. W naszym wypadku będzie to: swagger.json

Dodatkowo w celu dokonania bardziej przejrzystej weryfikacji różnic pomiędzy specyfikacjami, użyta została biblioteka json-diff, który służy do porównywania i różnicowania obiektów JSON w języku JavaScript. Te różnice mogą obejmować dodane, usunięte lub zmienione klucze i wartości.

Do zdefiniowania i wykonania samych testów wykorzystany został popularny framework do testowania jednostkowego i akceptacyjnego dla języka JavaScript, czyli mocha.

W celu uruchomienia testów wystarczy wykonać komendę npm run test, który de facto uruchamia skrypt open-api-diff-test.js:

const jsonAssertion = require("soft-assert");
const util = require("util");
const helper = require("./helper");
const fs = require("fs").promises;

const openApiSchemaName = 'petstore';
const schemaUrl = 'https://petstore.swagger.io/v2/swagger.json';
const dirnameGroundtruthFolderPath = 'schemas/groundtruth/';
const dirnameActualFolderPath = 'schemas/actual/';

describe('Pet Store API OpenAPI difference tests', async function () {
  it('Pet Store Test', async function () {
    const filenames = await fs.readdir(dirnameGroundtruthFolderPath);

    await helper.createActualSchemaFileFromUrl(schemaUrl, dirnameActualFolderPath)

    await Promise.all(filenames.map(async (file) => {
      const actualFilePath = `${dirnameActualFolderPath}${file}`;
      const groundTruthFilePath = `${dirnameGroundtruthFolderPath}${file}`;

      try {
        const [actualOpenAPI, groundTruthOpenAPI] = await Promise.all([
          fs.readFile(actualFilePath, 'utf8').then(JSON.parse),
          fs.readFile(groundTruthFilePath, 'utf8').then(JSON.parse)
        ]);

        await helper.createResultsFolderAndSaveSchemas(actualOpenAPI, groundTruthOpenAPI, openApiSchemaName);

        const openApiDiffResult = await helper.getOpenApiDiffResult(actualOpenAPI, groundTruthOpenAPI);
        const jsonDifferenceResult = await helper.getJsonDifferenceAndSaveResults(actualOpenAPI, groundTruthOpenAPI, openApiSchemaName, file);

        jsonAssertion.softAssert(openApiDiffResult.breakingDifferencesFound, false, `Warning! - There is a breaking change in your code - ${file}: ${util.inspect(openApiDiffResult, { depth: null })}`);
        jsonAssertion.softAssert(jsonDifferenceResult, undefined, `OpenAPI schema has changed. If change does not break the contract please update the following Groundtruth OpenApi Schema: ${file}`);
      } catch (error) {
        console.error(`Error processing file ${file}:`, error);
      }
    }));

    jsonAssertion.softAssertAll();
  }).timeout(120000);
});

Do wykonania wielu asercji w jednym skrypcie po sobie, bez ryzyka przerwania flow testu, użyta została biblioteka softAssert.

Jak widzicie w skrypcie znajdują się dwie asercje:

      jsonAssertion.softAssert(openApiDiffResult.breakingDifferencesFound, false, `Warning! - There is a breaking change in your code - ${file}: ${util.inspect(openApiDiffResult, { depth: null })}`);
      jsonAssertion.softAssert(jsonDifferenceResult, undefined, `OpenAPI schema has changed. If change does not break the contract please update the following Groundtruth OpenApi Schema: ${file}`);

Pierwsza sprawdza wystąpienie breaking changes przez narzędzie open-api-diff, druga zaś wykrywa wszelkie zmiany/różnice znalezione przez bibliotekę json-diff.

Przejdźmy teraz do uruchomienia testu. W sytuacji, kiedy obie specyfikacje będą takie same, zakończy się on sukcesem:

Usuńmy teraz endpoint /user/logout z pliku swagger.json znajdującego się w folderze schemas/actual (tak jak zrobiliśmy to poprzednio). Przed uruchomieniem testu jednak zakomentujmy tymczasowo kod znajdujący się w linii 15:

Jeśli pominiemy ten krok, swagger.json mieszczący się w folderze schemas/actual będzie nadpisywany tą samo wersją specyfikacji, której źródłem będzie URL. Po tej zmianie uruchommy jeszcze raz testy za pomocą komendy npm run test.

Tym razem testy nie zakończyły się sukcesem. Poniżej znajdziemy informacje o niepowodzeniach pochodzących z obu asercji:

  • Asercji narzędzia open-api-diff:
  • Asercji narzędzia json-diff:

Dodatkowo po zakończeniu testów w folderze results zostaną utworzone trzy pliki w formacie JSON:

  • petstore-ActualOpenAPI.json – aktualna specyfikacja – jego źródło stanowi URL

  • petstore-GroundTruthOpenAPI.json – specyfikacja wzorcowa

  • petstoreDiff.json – plik zawierajacy raport z różnicami wykrytami pomiędzy specyfikacjami, przykład takiego pliku znajduje się poniżej:

To wszystko!😊Od teraz dzięki zastosowaniu podejścia testowania kompatybilności wstecznej API za pomoca narzedzia Open API Diff jesteśmy w stanie skutecznie identyfikować zmiany mogące wpłynąć na istniejące implementacje serwisów. 

Aby nie przegapić kolejnego artykułu już teraz zapisz się do Newslettera.

Śledź nas w Social Media

Copyright © Automatyzacja.it 2023

  Partnerzy: