W dzisiejszym wpisie chciałbym podzielić się z Wami jednym ze sposobów tworzenia testów automatycznych Sanity warstwy REST API w narzędziu Postman. Podejście to pozwala na implementację tanich w utrzymaniu testów, bazujących na definiowaniu danych wejściowych w dokumentacji Open API Schema (Swagger) i automatycznym generowaniu kolekcji w Postmanie, które następnie są uruchamiane z odpowiednimi asercjami jako testy Sanity.
Chciałbym zaznaczyć, iż podane przeze mnie rozwiązanie nie służy do dogłębnego testowania funkcjonalności serwisów REST API, lecz przede wszystkim w sytuacjach, gdy po wprowadzeniu drobnych zmian w kompilacji oprogramowania, chcemy upewnić się, że te zmiany działają poprawnie, zanim przejdziemy do pełnego testowania regresji.
Northwind API
Northwind API to NodeJS’owa implementacja serwisu do obsługi danych o pracownikach oparta na popularnym framework’u webowym Express, którego jednym z głównych celów jest ułatwienie tworzenia serwerów HTTP oraz zarządzanie routingiem i obsługą żądań HTTP. Sam serwis posiada jeden zasób o nazwie employees, na którym możemy wykonywać operacje typu CRUD. W repozytorium GitHub Automatyzacja.IT, możecie sklonować lekko zmodyfikowaną i uproszczoną wersję serwisu.
Po sklonowaniu projektu zbudujmy go za pomocą komendy: npm install
, a następnie uruchommy serwis, komendą: npm start
Wart nadmienić, iż w projekcie zaimplementowano dokumentację OpenAPI (Swagger). W tym celu została użyte biblioteki: swagger-jsdoc oraz swagger-ui-expres.
Kluczową cechą Swagger JSDoc jest fakt, iż pozwala programistom dodawać komentarze JSDoc do swojego kodu źródłowego, które opisują endpointy API, ich parametry, odpowiedzi, i inne informacje dotyczące API. Następnie narzędzie to analizuje te komentarze i generuje dokumentację w formacie Swagger, który można następnie udostępnić jako interaktywną dokumentację API
Swagger-ui-express z kolei pozwala na łatwe dodanie dokumentacji opartej na Swaggerze do istniejącej aplikacji Express.
Aby zapoznać się z dokumentacji Swagger serwisu NorthWind API, wejdźmy w następujący link: http://localhost:3000/swagger/
Jak widzicie w serwisie NorthWind zostały zaimplementowane 4 operacje:
GET /employees – pobranie/wyświetlenie listy pracowników
POST /employees – dodanie nowego pracownika
GET /employees/{employeeId} – znalezienie pracownika o określonym Id
POST /employees/{employeeId} – edycja pracownika o określonym id
Każda z nich została dokładnie udokumentowana – poprzez dodanie odpowiednich komentarzy do kodu źródłowego kontrolera: employees-controller.js.
Na samej górze skryptu zostały opisane wszystkie schematy komponentów/modeli serwisu.
Spójrzmy na główny obiekt o nazwie Employee, który opisuje wszystkie dostępne atrybuty pracownika:
/**
* @openapi
* components:
* schemas:
* Employee:
* type: object
* properties:
* id:
* type: integer
* format: int32
* example: 1
* lastName:
* type: string
* example: Smith
* firstName:
* type: string
* example: Mike
* title:
* type: string
* example: Software Developer
* required: [ lastName, firstName, title ]
* EmployeeSansID:
* type: object
* properties:
* lastName:
* type: string
* example: Smith
* firstName:
* type: string
* example: Mike
* title:
* type: string
* example: Software Developer
* required: [ lastName, firstName, title ]
* EmployeeUpdate:
* type: object
* properties:
* lastName:
* type: string
* example: Smith
* firstName:
* type: string
* example: Mike
* title:
* type: string
* example: Software Developer
*/
W samym schemacie podawana jest nazwa, typ oraz przykładowa wartość, każdego z atrybutów oraz wskazanie, które z pól jest obowiązkowe.
Skupmy się na chwilę na wartościach examples znajdujących się w schemacie, które nie występowały oryginalnie w dokumentacji i zostały przeze mnie dodanie ręcznie:
example: Smith
example: Mike
example: Software Developer
Dzięki dodaniu tych przykładowych wartości możemy zauważyć, iż są one również widoczne w samej dokumentacji, przykładowo w ciele, żądania operacji dodającej nowego pracownika – POST /employees:
Teraz w łatwy sposób, dzięki poprawnie wypełnionym ciele żądania, możemy z sukcesem wywołać tę operację z poziomu Swagger’a:
Oprócz dodania przykładowych wartości do komponentów/modeli serwisu powinniśmy również uzupełnić takowe wartości dla żądań zawierających tkz. path parameters, czyli parametry wejściowe w endpoint’ach np. – GET /employees/{employeeId}
/**
* @openapi
* "/employees/{employeeId}":
* get:
* summary: Find employee by ID
* operationId: GetEmployeeByID
* tags: [ Employees ]
* parameters:
* - name: employeeId
* in: path
* required: true
* example: 1
* schema:
* type: integer
* format: int32
* responses:
* "200":
* description: successful operation
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Employee"
* "404":
* description: Employee not found
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Error"
*/
app.get('/employees/:employeeId', asyncHandler(async (req, res) => {
try {
res.json(await service.get(+req.params.employeeId));
} catch (error) {
if (error instanceof EmployeeNotFoundError) throw new ExpressError(404, error.message);
throw error;
}
}));
W powyższym opisie przykładowy parametr employeeId to 1 i ten właśnie parametr wejściowy widoczny jest również w samej dokumentacji Swagger’a:
Wygenerowanie kolekcji z pliku swagger.json w Postmanie
Mamy już dobrze opisaną dokumentacje Open API dla naszego serwisu wraz z examples, czas więc na wygenerowanie kolekcji wszystkich operacji Northwind API.
Wejdźmy zatem jeszcze raz w dokumentację Swagger’a, a następnie pobierzmy plik swagger.json.
Plik swagger.json jest plikiem w formacie JSON, który zawiera specyfikację API w formacie Swagger (OpenAPI Specification). Jest to struktura danych, która opisuje dostępne endpoint’y, parametry, odpowiedzi, schematy danych, zabezpieczenia i wiele innych informacji dotyczących interfejsu API:
{
"openapi":"3.0.0",
"info":{
"title":"Northwind",
"version":"1.0.0",
"description":"A simple REST API for providing basic CRUD-access to the employees in a Northwind database."
},
"paths":{
"/employees":{
"get":{
"summary":"Get all employees",
"operationId":"GetAllEmployees",
"tags":[
"Employees"
],
"responses":{
"200":{
"description":"Successful operation",
"content":{
"application/json":{
"schema":{
"type":"array",
"items":{
"$ref":"#/components/schemas/Employee"
}
}
}
}
}
}
},
"post":{
"summary":"Add a new employee",
"operationId":"AddEmployee",
"tags":[
"Employees"
],
"requestBody":{
"required":true,
"content":{
"application/json":{
"schema":{
"$ref":"#/components/schemas/EmployeeSansID"
}
}
}
},
"responses":{
"200":{
"description":"successful operation",
"content":{
"application/json":{
"schema":{
"type":"object",
"properties":{
"id":{
"type":"integer",
"format":"int32"
}
}
}
}
}
}
}
}
},
"/employees/{employeeId}":{
"get":{
"summary":"Find employee by ID",
"operationId":"GetEmployeeByID",
"tags":[
"Employees"
],
"parameters":[
{
"name":"employeeId",
"in":"path",
"required":true,
"example":1,
"schema":{
"type":"integer",
"format":"int32"
}
}
],
"responses":{
"200":{
"description":"successful operation",
"content":{
"application/json":{
"schema":{
"$ref":"#/components/schemas/Employee"
}
}
}
},
"404":{
"description":"Employee not found",
"content":{
"application/json":{
"schema":{
"$ref":"#/components/schemas/Error"
}
}
}
}
}
},
"put":{
"summary":"Updates an employee",
"operationId":"UpdateEmployee",
"tags":[
"Employees"
],
"parameters":[
{
"name":"employeeId",
"in":"path",
"required":true,
"example":1,
"schema":{
"type":"integer",
"format":"int32"
}
}
],
"requestBody":{
"content":{
"application/json":{
"schema":{
"$ref":"#/components/schemas/EmployeeUpdate"
}
}
}
},
"responses":{
"200":{
"description":"successful operation"
},
"404":{
"description":"Employee not found",
"content":{
"application/json":{
"schema":{
"$ref":"#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components":{
"schemas":{
"Employee":{
"type":"object",
"properties":{
"id":{
"type":"integer",
"format":"int32",
"example":1
},
"lastName":{
"type":"string",
"example":"Smith"
},
"firstName":{
"type":"string",
"example":"Mike"
},
"title":{
"type":"string",
"example":"Software Developer"
}
},
"required":[
"lastName",
"firstName",
"title"
]
},
"EmployeeSansID":{
"type":"object",
"properties":{
"lastName":{
"type":"string",
"example":"Smith"
},
"firstName":{
"type":"string",
"example":"Mike"
},
"title":{
"type":"string",
"example":"Software Developer"
}
},
"required":[
"lastName",
"firstName",
"title"
]
},
"EmployeeUpdate":{
"type":"object",
"properties":{
"lastName":{
"type":"string",
"example":"Smith"
},
"firstName":{
"type":"string",
"example":"Mike"
},
"title":{
"type":"string",
"example":"Software Developer"
}
}
},
"Error":{
"type":"object",
"properties":{
"message":{
"type":"string"
}
}
}
}
},
"tags":[
]
}
Mając już gotowy plik swagger.json uruchamiamy Postman’a w wersji desktopowej, klikamy w przycisk File, a następnie w przycisk Import. Pojawi nam się następujące okno:
Kolejny krok to Kliknięcie w select files i załadowanie pliku swagger.json. Następnie w kolejnym widoku wybierzmy: OpenAPI 3.0 with a Postman Collection. Ta opcja pozwoli nam stworzenie definicji schematu API oraz wygeneruje kolekcję na jego podstawie:
Ostatni krok to kliknięcie w View Import Settings. Tam, w opcji Parameter generation zaznaczamy opcję Example. Dzięki temu w żądaniach kolekcji zawarte będą przypisane przykładowe wartości atrybutów:
Wracamy do pierwszego okno i klikamy w przycisk import. W tym momencie Postman generuje definicję Schemy oraz kolekcję, które widoczne są z zakładki API’s. Otwierając żądanie odpowiedzialne za dodanie nowego pracownika, widzimy w body wszystkie potrzebne wartości, aby poprawnie wywołać tę konkretną operację. Oczywiście wcześniej powinniśmy przypisać wartość http://localhost:3000 do zmiennej {{baseURL}}.
Automatyczne generowanie i uruchomienie testów Postman na bazie pliku swagger.json
Manualne wywoływanie wygenerowanych z dokumentacji Swagger’a kolekcji requestów to jedno, aczkolwiek, warto byłoby ten proces zautomatyzować oraz dodać kilka niezbędnych elementów takich jak asercje sprawdzające poprawność statusów kodów odpowiedzi poszczególnych żądań.
W tym celu zaimplementowałem rozwiązanie o nazwie: postman-api-service, wykorzystujące serwis Postman API – dajacy dostęp do operacji pozwalających na zarządzanie wszystkim elementami Postman (workspace, schema, kolekcje, api), a co za tym idzie zautomatyzowanie całego procesu z poziomu API:
Spójrzmy teraz na poszczególne kroki tego procesu:
Pobranie Id API na podstawie nazwy
Jeśli istnieje usuń API w Postmanie, a następnie utwórz nowe API
Jeśli nie istnieje – utwórz nowe API
Stworzenie definicji schema na bazie swagger.json dla utworzonego w stepie pierwszym API.
Stworzenie kolekcji na podstawie definicji Schema.
Zedytowanie kolekcji poprzez dodanie – zmiennych kolekcji (baseUrl, OpenAPISchemaUrl) oraz skryptu testowego zawierającego asercje sprawdzające czy status code wywołanego żądania zakończył się sukcesem – skrypt odpowiedzialny za ten krok znajduje się tutaj.
Zastąpienie oryginalnej kolekcji na tę zedytowaną w stepie 4.
Uruchomienie testu za pomocą aplikacji newman, a po zakończeniu testów wygenerowanie raportu HTML.
Cały proces testowy zdefiniowany jest w skrypcie testrunner.js i uruchamiany za pomocą komendy: npm run test
Do jego poprawnego uruchomienia konieczne jest ustawienie kilku zmiennych środowiskowych w pliku .env (trzeba go utworzyć na nowo). Wszystkie potrzebne zmienne zdefiniowane są w pliku env.sample:
API_KEY=
API_ENDPOINT=http://localhost:3000
API_SWAGGER_URL=/swagger.json
API_URL=https://api.getpostman.com
API_TITLE=Northwind-API
WORKSPACE_ID=
Zmienna API_KEY – to klucz API przypisany do konta utworzonego w Postman i musi być on ręcznie wygenerowany w ustawieniach profilu settings -> API keys:
Zmienna WORKSPACE_ID to id przestrzeni roboczej w Postmanie:
Uruchomienie procesu testowego
Uruchommy teraz testy API za pomocą komendy: npm run tes
t. Po krótkim czasie naszym oczom powinny ukazać się wyniki przeprowadzonych testów – najpierw w terminalu:
A następnie w postaci pliku HTML dostępnego w lokalizacji: postman-api-service\results\report.html:
Same testy możemy również uruchomić bezpośrednio z aplikacji Postman w sposób ręczny bez wykonywania, żadnych dodatkowych kroków:
To wszystko! 😊 Dzięki zastosowaniu podejścia pozwalającego na automatyczne generowane testów REST API w Postman na bazie dokumentacji Swaggers jesteśmy w stanie w łatwy sposób oraz stosunkowo niskim nakładem pracy zapewnić minimalne potrzebne pokrycie testami na poziomie Sanity.
Jak zwykle gotowy projekt z rozwiązaniem możecie znaleźć w repozytorium na GitHub Automatyzacja.It.
Aby nie przegapić kolejnego artykułu już teraz zapisz się do Newslettera.