Blog

Automatycznie generowane testy REST API w Postman na bazie dokumentacji Swagger

Automatycznie generowane testy REST API w Postman na bazie dokumentacji Swagger

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:

  1. 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

  2. Stworzenie definicji schema na bazie swagger.json dla utworzonego w stepie pierwszym API.

  3. Stworzenie kolekcji na podstawie definicji Schema.

  4. 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.

  5. Zastąpienie oryginalnej kolekcji na tę zedytowaną w stepie 4.

  6. 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 test. 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.

Śledź nas w Social Media

Copyright © Automatyzacja.it 2023

  Partnerzy: