DEV Community

Cover image for Framework de Tests Automatisés API avec Pytest: Tutoriel Pratique
Antoine Laurent
Antoine Laurent

Posted on • Originally published at apidog.com

Framework de Tests Automatisés API avec Pytest: Tutoriel Pratique

Les développeurs Python choisissent souvent pytest pour tester des API parce qu’il reste simple : un test est une fonction test_*, une assertion est un assert, et le runner s’occupe de l’exécution. Avec requests, vous obtenez une base légère et maintenable pour automatiser des tests d’API directement en code.

Essayez Apidog aujourd’hui

Dans ce guide, vous allez construire une suite de tests d’API avec pytest : initialisation du projet, premier test HTTP, fixtures partagées, tests paramétrés, assertions sur les réponses et validation de schéma JSON. Les exemples utilisent une API fictive de style réaliste pour que vous puissiez adapter la structure à votre propre backend.

Configuration du projet

Créez un environnement virtuel, puis installez les dépendances :

python -m venv .venv
source .venv/bin/activate
pip install pytest requests jsonschema
Enter fullscreen mode Exit fullscreen mode

Utilisez une structure simple dès le départ :

api-tests/
  conftest.py        # fixtures partagées
  test_users.py      # tests des endpoints utilisateurs
  test_orders.py     # tests des endpoints commandes
  pytest.ini         # configuration pytest
Enter fullscreen mode Exit fullscreen mode

Pytest découvre automatiquement les tests si vous respectez ces conventions :

  • les fichiers commencent par test_ ou se terminent par _test.py ;
  • les fonctions commencent par test_ ;
  • les classes commencent par Test et n’ont pas de méthode __init__.

Exemple :

test_users.py
test_orders.py
Enter fullscreen mode Exit fullscreen mode

Si les tests automatisés sont nouveaux pour vous, commencez par cette introduction sur ce qu'est le test automatisé.

Pourquoi pytest pour les tests d’API ? requests gère les appels HTTP, tandis que pytest fournit la découverte des tests, les assertions lisibles, les fixtures, la paramétrisation et le reporting. Vous pouvez donc garder vos tests proches du code applicatif, dans le même dépôt, et détecter les régressions directement dans les pull requests.

Écrire votre premier test d’API

Un test d’API pytest suit généralement trois étapes :

  1. envoyer une requête ;
  2. lire la réponse ;
  3. vérifier le statut, le corps ou les en-têtes.

Exemple avec un endpoint utilisateur :

import requests

BASE_URL = "https://api.example.com/v1"

def test_get_user_returns_200():
    response = requests.get(f"{BASE_URL}/users/42")

    assert response.status_code == 200

def test_get_user_returns_expected_fields():
    response = requests.get(f"{BASE_URL}/users/42")
    body = response.json()

    assert body["id"] == 42
    assert "email" in body
    assert body["status"] == "active"
Enter fullscreen mode Exit fullscreen mode

Lancez la suite :

pytest -v
Enter fullscreen mode Exit fullscreen mode

En cas d’échec, pytest affiche les valeurs réelles et attendues à partir de simples instructions assert. Vous n’avez pas besoin de méthodes d’assertion spécifiques.

Pour approfondir les vérifications utiles sur une réponse HTTP, consultez ce guide sur les assertions d'API.

Partager la configuration avec les fixtures

Évitez de répéter l’URL de base, les headers ou la session HTTP dans chaque test. Utilisez des fixtures.

Une fixture est une fonction décorée avec @pytest.fixture. Un test l’utilise simplement en déclarant son nom comme paramètre.

Placez les fixtures communes dans conftest.py :

# conftest.py
import pytest
import requests

BASE_URL = "https://api.example.com/v1"

@pytest.fixture(scope="session")
def api_session():
    session = requests.Session()
    session.headers.update({"Accept": "application/json"})

    yield session

    session.close()

@pytest.fixture
def auth_token(api_session):
    response = api_session.post(
        f"{BASE_URL}/auth/login",
        json={
            "email": "qa@example.com",
            "password": "test-pass",
        },
    )

    return response.json()["token"]
Enter fullscreen mode Exit fullscreen mode

Ici :

  • scope="session" crée une seule session HTTP pour toute l’exécution ;
  • le code avant yield prépare la ressource ;
  • le code après yield nettoie la ressource.

Un test peut ensuite demander uniquement ce dont il a besoin :

def test_create_order(api_session, auth_token):
    response = api_session.post(
        f"{BASE_URL}/orders",
        headers={"Authorization": f"Bearer {auth_token}"},
        json={
            "product_id": 7,
            "quantity": 2,
        },
    )

    assert response.status_code == 201
    assert response.json()["status"] == "pending"
Enter fullscreen mode Exit fullscreen mode

Les fixtures remplacent avantageusement les anciens modèles setup_function et teardown_function. Elles se composent mieux, rendent les dépendances explicites et supportent plusieurs scopes. La documentation officielle des fixtures pytest les recommande comme approche par défaut.

Exécuter un test sur plusieurs entrées

Les endpoints doivent être testés avec plusieurs valeurs : cas valides, invalides et limites. Au lieu de dupliquer les fonctions, utilisez @pytest.mark.parametrize.

import pytest

BASE_URL = "https://api.example.com/v1"

@pytest.mark.parametrize(
    "user_id, expected_status",
    [
        (42, 200),
        (99999, 404),
        (0, 404),
        (-1, 400),
    ],
)
def test_get_user_status_codes(api_session, user_id, expected_status):
    response = api_session.get(f"{BASE_URL}/users/{user_id}")

    assert response.status_code == expected_status
Enter fullscreen mode Exit fullscreen mode

Cette fonction génère quatre cas de test indépendants. Si un cas échoue, les autres continuent à s’exécuter et apparaissent séparément dans le rapport.

Quand la liste d’entrées devient longue, chargez les données depuis un fichier CSV ou JSON. Ce guide sur les tests d'API pilotés par les données avec CSV et JSON couvre ce modèle.

Si vous hésitez sur les statuts HTTP à attendre, utilisez cette référence sur les codes de statut HTTP que les API REST devraient utiliser.

Assertions sur le corps de réponse et le schéma

Un code 200 ne suffit pas. Une réponse peut réussir côté HTTP tout en renvoyant un JSON incomplet ou incorrect.

Commencez par des assertions directes sur le corps :

def test_order_response_shape(api_session, auth_token):
    response = api_session.post(
        f"{BASE_URL}/orders",
        headers={"Authorization": f"Bearer {auth_token}"},
        json={
            "product_id": 7,
            "quantity": 2,
        },
    )

    body = response.json()

    assert isinstance(body["id"], int)
    assert body["quantity"] == 2
    assert body["total"] > 0
    assert response.elapsed.total_seconds() < 1.0
Enter fullscreen mode Exit fullscreen mode

Pour vérifier toute la structure de la réponse, validez le JSON contre un schéma :

from jsonschema import validate

order_schema = {
    "type": "object",
    "required": ["id", "product_id", "quantity", "status", "total"],
    "properties": {
        "id": {"type": "integer"},
        "product_id": {"type": "integer"},
        "quantity": {"type": "integer", "minimum": 1},
        "status": {"type": "string"},
        "total": {"type": "number"},
    },
}

def test_order_matches_schema(api_session, auth_token):
    response = api_session.post(
        f"{BASE_URL}/orders",
        headers={"Authorization": f"Bearer {auth_token}"},
        json={
            "product_id": 7,
            "quantity": 2,
        },
    )

    validate(instance=response.json(), schema=order_schema)
Enter fullscreen mode Exit fullscreen mode

La validation de schéma détecte les champs manquants, renommés ou de mauvais type. Elle évite aussi de multiplier les assertions champ par champ.

La bibliothèque jsonschema est le choix courant pour ce besoin. Sa documentation de validation détaille les mots-clés disponibles.

Exécuter la suite en CI

Une suite pytest devient réellement utile lorsqu’elle s’exécute automatiquement à chaque changement.

Pytest renvoie un code de sortie non nul si un test échoue, ce qui permet à votre pipeline CI de bloquer un build ou une pull request.

Générez aussi un rapport JUnit :

pytest -v --junitxml=results.xml
Enter fullscreen mode Exit fullscreen mode

Cette commande peut être appelée depuis GitHub Actions, GitLab CI, Jenkins ou tout autre outil CI.

Pour une configuration complète, y compris les secrets et la sélection d’environnement, consultez cette présentation des tests d'API dans les pipelines CI/CD.

Deux pratiques rendent la suite plus fiable en CI.

D’abord, ne codez pas en dur les secrets ni les URL d’environnement. Lisez-les depuis les variables d’environnement :

import os

BASE_URL = os.environ.get(
    "API_BASE_URL",
    "https://staging.example.com/v1",
)
Enter fullscreen mode Exit fullscreen mode

Ensuite, exécutez les tests indépendants en parallèle si la suite devient lente. Le plugin pytest-xdist permet de répartir les tests sur plusieurs cœurs :

pip install pytest-xdist
pytest -n auto
Enter fullscreen mode Exit fullscreen mode

Les tests parallèles exigent des données isolées. Évitez les tests qui dépendent de l’ordre d’exécution ou d’un état partagé.

Maintenir une suite pytest

Une suite de cinquante tests reste simple à gérer. Une suite de plusieurs centaines nécessite des conventions.

Regroupez les tests par domaine :

test_users.py
test_orders.py
test_payments.py
Enter fullscreen mode Exit fullscreen mode

Utilisez les classes seulement lorsqu’elles apportent une configuration commune. Des fonctions explicites dans des modules bien nommés sont souvent plus lisibles.

Ajoutez des marks pour exécuter des sous-ensembles :

import pytest

@pytest.mark.smoke
def test_get_user_returns_200(api_session):
    response = api_session.get(f"{BASE_URL}/users/42")

    assert response.status_code == 200
Enter fullscreen mode Exit fullscreen mode

Déclarez-les dans pytest.ini :

[pytest]
markers =
    smoke: tests rapides à exécuter à chaque commit
    slow: tests plus longs à exécuter moins souvent
Enter fullscreen mode Exit fullscreen mode

Puis lancez uniquement les tests smoke :

pytest -m smoke
Enter fullscreen mode Exit fullscreen mode

Centralisez aussi la configuration :

  • URL de base ;
  • headers communs ;
  • fixtures d’authentification ;
  • schémas JSON ;
  • helpers de création ou nettoyage de données.

Ne dupliquez pas ces éléments entre les fichiers de test. Si vous écrivez la même logique deux fois, extrayez-la dans une fixture ou une fonction utilitaire.

Cette discipline est la même que pour tout framework de test automatisé. Ce guide sur comment écrire des scripts de test automatisés détaille ces principes.

Quand utiliser plutôt une plateforme

Un framework pytest est adapté si votre équipe écrit en Python et veut conserver les tests dans le même dépôt que le code applicatif.

Il devient moins pratique lorsque :

  • les équipes QA ou produit doivent contribuer sans écrire de Python ;
  • vous voulez concevoir, mocker et tester les API dans un même outil ;
  • vous ne souhaitez pas maintenir de code de fixtures, de helpers et d’assertions.

Apidog couvre ce cas d’usage. Il propose la construction visuelle de tests, la validation de schéma par rapport à une spécification OpenAPI, des exécutions pilotées par CSV ou JSON, et un runner CLI pour la CI.

Beaucoup d’équipes combinent les deux approches : pytest pour les scénarios avec beaucoup de logique métier, et Apidog pour une couverture plus large, la conception d’API et le mocking. Vous pouvez télécharger Apidog et comparer les deux approches sur un endpoint réel.

Questions fréquemment posées

Pourquoi utiliser pytest plutôt que le module unittest intégré de Python pour les tests d’API ?

Pytest demande moins de boilerplate. Les tests sont de simples fonctions, les assertions sont de simples assert, et les fixtures gèrent la configuration plus souplement que les méthodes de classes de unittest.

Pytest dispose aussi d’un vaste écosystème de plugins et d’un support intégré de parametrize pour les tests pilotés par les données. Il peut toujours exécuter des tests existants de style unittest, ce qui facilite une migration progressive.

Quelle est la différence entre une fixture et parametrize ?

Une fixture fournit une ressource réutilisable à un test : session HTTP, token d’authentification, utilisateur de test, configuration, etc.

parametrize exécute le même test plusieurs fois avec différentes valeurs d’entrée.

En pratique :

  • les fixtures partagent la configuration ;
  • parametrize multiplie les cas de test ;
  • les deux peuvent être utilisés ensemble.

Devrais-je faire des assertions sur le temps de réponse dans les tests d’API pytest ?

Vous pouvez utiliser response.elapsed.total_seconds() pour détecter de fortes régressions :

assert response.elapsed.total_seconds() < 1.0
Enter fullscreen mode Exit fullscreen mode

Gardez toutefois une limite assez large. Pytest est adapté aux tests fonctionnels, pas aux tests de charge. Pour mesurer précisément les performances, utilisez un outil dédié.

Comment garder les tests d’API indépendants dans pytest ?

Chaque test doit créer ou recevoir ses propres données via des fixtures. Évitez de dépendre :

  • de l’ordre d’exécution ;
  • d’une ressource créée par un autre test ;
  • d’un état partagé non nettoyé.

Des tests indépendants sont plus faciles à paralléliser, à déboguer et à exécuter isolément.

Pytest peut-il valider les réponses par rapport à une spécification OpenAPI ?

Pytest ne le fait pas directement. Vous pouvez valider les réponses avec jsonschema, ou ajouter des plugins capables de vérifier les réponses contre un document OpenAPI.

Si cette validation est centrale dans votre workflow, une plateforme comme Apidog peut automatiser la validation contre votre spécification OpenAPI et réduire la configuration côté pytest.

Top comments (0)