top of page
Zoeken
  • Foto van schrijverRobin Mackaij

OpenAPI validatie met Robot Framework

Bijgewerkt op: 5 feb. 2022

Veel JSON REST API's bieden tegenwoordig een Swagger pagina als documentatie als een manier om de API te verkennen (zie ook Generate-Swagger-UI-from-Spring-REST-Docs @ blog.jdriven.com ) Deze Swagger pagina (aka Swagger UI) wordt vaak gebruikt door testers om te interacteren met de API om handmatig de API calls te construeren en verifiëren die vervolgens worden geïmplementeerd als een geautomatiseerde test.


Wat je je misschien niet realiseert, is dat de Swagger UI gegenereerd wordt uit een openapi.json of openapi.yaml bestand dat gehost wordt door de API server. Om "Swagger compatibiliteit" te verzekeren, moet dit de OpenAPI Specificaties volgen.


Maar als een webpagina die kan interacteren met de API wordt gegenereerd vanuit dit document, zou het dan niet mogelijk moeten zijn om ook test cases voor deze API te genereren?


Let’s find out!


Het genereren van de testgevallen

Het zal geen verrassing zijn dat het inderdaad mogelijk is om de testgevallen te genereren uit het OpenAPI-document. De paths van het document beschrijven alle beschikbare endpoints, de operations die voor het endpoint worden ondersteund en voor elke operation, de mogelijke antwoorden

JSON

"/wagegroups": {
    "post": {
        "summary": "Post Wagegroup",
        "operationId": "post_wagegroup_wagegroups_post",
        "requestBody": { *snip* },
        "responses": {
            "201": {
                "description": "Successful Response",
                "content": { *snip* }
            },
            "418": {
                "description": "I'm a Teapot",
                "content": { *snip* }
            },
            "422": {
                "description": "Validation Error",
                "content": { *snip* }
            }
        }
    }
}

In dit voorbeeld zien we dat, voor het /wagegroups endpoint, we 3 test gevallen hebben voor de post operation (200, 418 en 422 responses).


Nu is het schrijven van die 3 testgevallen met de hand niet zo veel moeite, maar endpoints die voor een specifieke resource zijn hebben over het algemeen meer methoden; get om de details voor een resource op te halen, put om ze bij te werken en delete om ze te verwijderen. Al deze methoden kunnen resulteren in verschillende antwoorden, dus het is niet ongewoon om 5-10 testgevallen te hebben voor een enkel endpoint. Nu is het schrijven van al deze test cases met de hand al snel een hoop werk, dus zou het leuk zijn om in plaats daarvan deze test cases te genereren.


Binnen het Robot Framework, doet de DataDriver library precies dat; test cases genereren op basis van een data source. Een typische DataDriver test suite ziet er ongeveer zo uit:

ROBOTFRAMEWORK

*** Settings ***
Library           DataDriver
Resource          openapi.json
Test Template     Test Endpoint

*** Test Case ***
Test Endpoint for ${method} on ${endpoint} where ${status_code} is expected

*** Keywords ***
Test Endpoint
    [Arguments]    ${endpoint}    ${method}    ${status_code}
    Perform Magic    endpoint=${endpoint}    method=${method}    status_code=${status_code}

Wat DataDriver zal doen, is het keyword waarnaar verwezen wordt in het Test Template (in dit geval Test Endpoint) uitvoeren op elke gegenereerde test case, waarbij de gespecificeerde parameters (${endpoint}, ${method} en ${status_code}) doorgegeven worden aan het Test Endpoint key word. Het Test Endpoint keyword zal op zijn beurt Perform Magic uitvoeren voor elke test case.


Performing Magic

Dus het genereren van de testgevallen is eenvoudig...maar hoe werkt Perform Magic?


Het goede nieuws is dat er voor een groot deel van de testgevallen weinig magie nodig is: wat gedaan moet worden is een (http) request construeren en het antwoord moet in overeenstemming zijn met het OpenAPI document. Het OpenAPI document bevat ook alle informatie die nodig is om het request te construeren.


Laten we eens kijken naar ons vorige voorbeeld. Om de test case te valideren voor de post request op /wagegroups dat resulteert in een 201 response, kunnen we de benodigde gegevens vinden in ons OpenAPI document:



JSON

"post": {
    "summary": "Post Wagegroup",
    "operationId": "post_wagegroup_wagegroups_post",
    "requestBody": {
        "content": {
            "application/json": {
                "schema": {
                    "$ref": "#/components/schemas/WageGroup"
                }
            }
        },
        "required": true
    },
    "responses": {
        "201": {
            "description": "Successful Response",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/WageGroup"
                    }
                }
            }
        }
    }
}

In het bovenstaande snippet zien we een verwijzing naar het WageGroup schema, dat ook te vinden is in het OpenAPI document:

JSON

"WageGroup": {
    "title": "WageGroup",
    "required": [
        "id",
        "hourly_rate"
    ],
    "type": "object",
    "properties": {
        "id": {
            "title": "Id",
            "type": "string"
        },
        "hourly_rate": {
            "title": "Hourly Rate",
            "type": "number"
        }
    }
}

Dus om een geldig request te doen, moeten we een JSON body geven met een id van het type string en een Hourly_Rate dat een Number is. In dit eenvoudige voorbeeld zijn er geen verdere beperkingen (zoals een minimum of maximum lengte voor het id of een minimum of maximum waarde voor de Hourly_Rate) maar met dergelijke beperkingen kan ook rekening worden gehouden bij het genereren van willekeurige waarden voor de gewenste eigenschappen.


Voor Contract Testing werkt het gebruik van willekeurig gegenereerde gegevens goed. Hoewel de naam 32d18a7e7e574f55bc2958fcea5b4a66 geen typische naam is voor een wachtwoord, is het net zo goed een string als Joe, Jane of Andrianampoinimerinatompokoindrindra. Hetzelfde geldt voor andere datatypes. Vergeet niet dat de gegevens het contract moeten volgen, ze hoeven niet logisch te zijn vanuit een zakelijk oogpunt.

Aangezien het aantal datatypes dat door de OpenAPI specificatie wordt ondersteund beperkt is, is het niet erg moeilijk om de vereiste data voor de meeste API endpoints te genereren. Validatie van de respons ten opzichte van het OpenAPI document is ook vrij eenvoudig; er zijn open source pakketten beschikbaar om een respons ten opzichte van een schema te verifiëren en aanvullende validaties (b.v. zijn er geen extra properties aanwezig in de respons?) zijn ook niet al te ingewikkeld.


Dus zonder echt magie uit te voeren, is het genereren van de testgevallen en het uitvoeren ervan heel goed mogelijk.


Robot Framework OpenApiDriver

Gebaseerd op de hierboven besproken ideeën, heb ik de OpenApiDriver gemaakt. De OpenApiDriver is een gespecialiseerde library gebaseerd op DataDriver die het genereren van de testgevallen afhandelt en een aantal keywords levert om te valideren of de API implementatie overeenkomt met het OpenAPI document dat door de API wordt geleverd.


De belangrijkste testsuite voor de acceptatietests van het project ziet er zo uit en kan als voorbeeld dienen voor als je de library eens wilt proberen in je eigen project.


ROBOTFRAMEWORK

*** Settings ***
Library             OpenApiDriver
...                 source=http://localhost:8000/openapi.json
...                 origin=http://localhost:8000
...                 base_path=${EMPTY}
...                 mappings_path=${root}/tests/user_implemented/custom_user_mappings.py
...                 response_validation=INFO
...                 require_body_for_invalid_url=${TRUE}

Test Template       Validate Test Endpoint Keyword

*** Test Cases ***
Test Endpoint for ${method} on ${endpoint} where ${status_code} is expected

*** Keywords ***
Validate Test Endpoint Keyword
    [Arguments]    ${endpoint}    ${method}    ${status_code}
    IF    ${status_code} == 404
        Test Invalid Url    endpoint=${endpoint}    method=${method}
    ELSE
        Test Endpoint
        ...    endpoint=${endpoint}    method=${method}    status_code=${status_code}
    END

Natuurlijk speelt er meer dan wat hierboven werd besproken. Heb je de mappings_path parameter opgemerkt en je afgevraagd waar die custom_user_mappings.py module over gaat? Heeft uw API eigenschappen die uniek moeten zijn (bijv. het emailadres of de loginnaam van een werknemer)? Zijn er resources met eigenschappen die verwijzen naar de id van een andere resource? Misschien gelden voor sommige eigenschappen beperkingen die niet in het OpenAPI document kunnen worden uitgedrukt?


Dit zijn allemaal problemen die ik tegenkwam toen ik OpenApiDriver implementeerde om een (C++) API te valideren zoals die werd geïmplementeerd volgens de OAS specificatie en oplossingen voor deze problemen zitten al in de library. "Stay tuned" voor een toekomstige post die dieper ingaat op deze geavanceerde use cases!


Tegelijkertijd zijn er vele manieren om een API te implementeren die voldoet aan de OAS en niet alles wat door de specificatie wordt ondersteund, wordt op dit moment ondersteund door de OpenApiDriver. De ontwikkeling gaat door en ik ben erg benieuwd naar uw feedback. Wat werkt voor u? Wat mist u nog? Neem gerust contact met me op via robin.mackaij@enqore.tech of plaats een issue in de repo!



102 weergaven0 opmerkingen

Recente blogposts

Alles weergeven
bottom of page