Blog Arolla

Tests unitaires Python pour une API externe

Ceci est une adaptation française de l'article Python REST API Unit Testing for External APIs sur pytest-with-eric.com, retravaillé pour être parfaitement compris par des développeurs s'exprimant dans la langue de Molière. Cependant, des améliorations concernant l'article original ont été réalisées pour davantage apporter de la valeur aux lecteurs de tout niveau. Cela dit, n'hésitez pas à suivre le travail de l'auteur original

Python code

Imaginez que vous ayez la tâche de construire un service qui communique avec une API externe.

Pour cette raison, vous décidez d'utiliser la librairie Requests ou une librairie similaire, vous vous servez de méthodes GET et POST par ci par là et tada ! On est bon !

Simple comme bonjour hein ?

En fait, pas vraiment. Si on fait ça comme hobby ou comme un projet personnel juste pour jouer avec les technos, alors oui c'est aussi simple que ça. Mais, en tant que développeur professionnel, nous avons la responsabilité de gérer les surprises et les cas limite qui peuvent accompagner notre code.

En ce qui nous concerne, le défi auquel nous devons faire face quand nous travaillons avec des APIs externes est que leurs comportements sont hors de notre contrôle. Ainsi, il y a beaucoup de problèmes qui peuvent pointer le bout de leur nez sans demander notre avis :
Des changements de schémas ou de charges utiles (payload), de nouveaux codes d'erreur ou encore une mise à jour des plafonds de vitesse

Bien sûr, une bonne API externe fera des release notes et veillera à la rétro-compatibilité de la nouvelle version de l'API (en d'autres termes, que l'ancienne version continue de marcher).

Mais le monde réel n'est pas un monde idéal. Tant et si bien que parfois, les changements viennent sans crier gare et fracasse notre système.

Comment donc s'en protéger ? En réalité, vous ne pouvez pas. En revanche, vous pouvez contrôler VOTRE code. Et c'est là que les tests unitaires entrent en scène.

Le test unitaire des API REST en Python aide à concevoir le code tel qu'il peut gérer avec élégance la plupart des cas limite liés aux APIs externes.

Dans cet article, nous allons partager certaines pratiques essentielles pour produire des tests unitaires de qualité lorsque notre code Python fait appel à une API externe.

Nous verrons de vrais exemples d'implémentation de tout ça. Le lien vers le code GitHub de l'article original se trouve d'ailleurs ci-dessous.

Lien vers le code sur GitHub


Note du traducteur
Pour ma part, je vous propose une version refactorée du code original que vous pouvez trouver sur ce lien. D'ailleurs, les extraits de code que je présente dans cet article sont issus de ce refactoring.

Prenez ces exemples pour ce qu'ils sont : des exemples. Par conséquent, n'hésitez pas si besoin à vous en inspirer quelque soit votre langage de programmation 🙂


Qu’est-ce qu’une API REST ?

Pour faire simple, une API REST est un système de communications entre deux ou plusieurs programmes qui se conforme aux contraintes architecturales REST (Representation State Transfer).

Fondamentalement, les API REST sont sans état. En d'autres termes, aucun état n'est maintenu entre le client et le serveur, et chaque requête client doit inclure toutes les informations requises par le serveur pour qu'il puisse la traiter.

Si vous voulez comprendre avec plus de détails ce qu'est une API REST, un bon point de départ serait l'article What is a REST API? d'IBM. Il explique minutieusement le style architectural REST.

Pourquoi utiliser des API REST ?

Depuis l'aube d'internet, partager des données entre applications a de l'importance.

Par contre, sans une API, partager des données entre des outils et des applications serait presque impossible.

Chaque service a un modèle de données personnalisé. Par conséquent, l'utilisation de formats d'échange uniformisés (comme JSON ou XML) a rendu l'échange de données possible.

Certains exemples dans lesquels l'utilisation d'API est possible incluent :

  • Partager des données de santé de votre Smartwatch à une appli de fitness pour recevoir des recommendations personnalisées
  • Envoyer des données de marketing, de ventes ou de CRM à un outil d'agrégation pour faire des comparaisons de performance
  • Obtenir des informations météo ou de géolocalisation d’un fournisseur externe pour estimer son heure d’arrivée
  • Collecter des données sur le cours des actions et dire à des clients s’ils peuvent en acheter ou non
  • Récupérer les dernières valeurs de taux de change si vous êtes une entreprise qui travaillent dans le marché des changes

La liste est sans fin.

Une simple API REST — Exemple de code

Rentrons dans le vif du sujet avec un exemple de code

Pour cet exemple, nous allons utiliser l'API Dog pour récupérer des données à propos des chiens.

Pourquoi une API sur les chiens ? Parce que chaque jour est un jour pour les chiens 🙂

C'est une API simple qui utilise une clé d'API qui est envoyée par mail après que vous vous soyez inscrit. Et c'est totalement gratuit (jusqu'à 10000 requêtes par mois).


Note du traducteur
Ne me regardez pas comme ça. Si vous préférez les chats, il y a l'API Cat qui fait la même chose mais pour les chats. Allez pas de jaloux 🙂


Préréquis

1. Obtenir une clé d’API et la stocker dans une variable d’environnement.

Cette clé d'API a besoin d'être renvoyée comme header lorsqu'on fait des requêtes à l'API.

Étrangement, même si la documentation dit qu'une clé d'API est nécessaire, les requêtes GET ont l'air de fonctionner sans 😕.


Note du traducteur
En fait, c'est normal. Sur la documentation Postman de l'API, on peut remarquer que la clé d'API n'est pas nécessaire pour toutes les requêtes, notamment les requêtes GET qui sont faites dans cet article (List Breeds et Search Breeds).

Toutefois, pour la requête POST que nous ferons (Votes), la clé d'API est bel et bien nécessaire


2. Logique principale du code

core.py


	logging.basicConfig(level=logging.INFO)


	class RequestType(Enum):
		"""
		Enum class for RequestType contenant 4 valeurs - GET, POST, PUT, PATCH, DELETE

		"""

		GET = "GET"
		POST = "POST"
		PATCH = "PATCH"
		DELETE = "DELETE"


	class DogAPI:
		def __init__(self):
			"""
			Function to initialize the Dog API class
			"""

			api_key = os.environ.get("API_KEY")
			self.headers = {
				"Content-Type": "application/json",
				"x-api-key": api_key,
			}
			self.base_url = "https://api.thedogapi.com/v1"

		def call_api(self, request_type: str, endpoint: str, payload: dict | str = None):
			"""Function to call the API via the Requests Library

			Args:
				request_type (str): Type of Request
				endpoint (str): API Endpoint
				payload (dict | str, optional): API Request Parameters or Query String. Defaults to None.
			Returns:
				Response (list | dict): JSON formatted response
			"""

			try:
				response = ""

				if request_type == "GET":
					response = requests.get(
						endpoint,
						timeout=30,
						payload=payload,
					)
				elif request_type == "POST":
					response = requests.post(
						endpoint,
						headers=self.headers,
						timeout=30,
						json=payload,
					)

				if response.status_code in (200, 201):
					return response.json()
				elif response.status_code == 401:
					return json.dumps(
						{"ERROR": "Authorization Error. Please check API key"}
					)

				response.raise_for_status()

			except requests.exceptions.HTTPError as errh:
				logging.error(errh)
			except requests.exceptions.ConnectionError as errc:
				logging.error(errc)
			except requests.exceptions.Timeout as errt:
				logging.error(errt)
			except requests.exceptions.RequestException as err:
				logging.error(err)

		def list_breeds(self, query_dict: dict) -> list:
			"""Function to list dog breeds

			Args:
				query_dict (dict): Query string parameters

			Returns:
				list: JSON formatted list of all dog breeds on the API
			"""

			if not isinstance(query_dict, dict):
				raise ValueError("ERROR - Parameter 'query_dict' should be of type dict")

			breeds_url = f"{self.base_url}/breeds"

			response = self.call_api(
				request_type=RequestType.GET.value,
				endpoint=breeds_url,
				payload=query_dict,
			)

			return response

		def search_breeds(self, query_str: str) -> list:
			"""Function to search dog breeds

			Args:
				query_str (str): Dog breed we are looking for

			Returns:
				list: JSON formatted response
			"""

			if not isinstance(query_str, str):
				raise ValueError("ERROR - Parameter 'query_str' should be of type string")

			search_breeds_url = f"{self.base_url}/breeds/search"

			response = self.call_api(
				request_type=RequestType.GET.value,
				endpoint=search_breeds_url,
				payload={"q": query_str},
			)

			return response

		def create_vote(self, payload: dict) -> dict:
			"""Function to vote on dog image

			Args:
				payload (dict): API request parameters

			Returns:
				dict: JSON formatted response
			"""

			if not self._is_payload_valid(payload):
				raise ValueError("ERROR - Parameter 'payload' should be of type dict")

			create_vote_url = f"{self.base_url}/votes"

			response = self.call_api(
				request_type=RequestType.POST.value,
				endpoint=create_vote_url,
				payload=payload,
			)

			return response

		def _is_payload_valid(self, payload):
			return (
				not isinstance(payload, dict)
				or "image_id" not in payload
				or "value" not in payload
			)

L'extrait de code ci-dessus contient une classe DogAPI utilisant les fonctionnalités suivantes de l'API :

  • List Breeds (méthode GET) : liste des races
  • Search Breeds (méthode GET) : recherche de races
  • Create Vote (méthode POST) : créer un vote

Par ailleurs, le code contient une méthode qui sert de wrapper autour de la librairie requests : call_api.

Ensuite, notons qu'on utilise la classe RequestType, qu'on fait hériter de Enum, pour gérer chaque type de méthode d'API REST (GET, POST, PUT, PATCH, DELETE).

Comment tester des API externes ?

Nous avons mis en place la logique principale du code source. Désormais, il est temps de rentrer un peu plus dans l'idée maîtresse de cet article : le test unitaire Python sur du code qui fait appel à une API externe.

Pour commencer, quels tests devrions-nous faire ?

Tests fonctionnels

Est-ce que le code fait ce qu'il est supposé faire ? Produit-il les bonnes données et/ou résultats ?

Voici un exemple de quelques tests fonctionnels qu'on peut trouver dans le repo sous le répertoire tests/. Qui plus est, les variables d'environnement utilisées pour le test unitaire sont spécifiées dans le fichier pytest.ini.


Note du traducteur (oui, encore moi)
Contrairement à l'architecture du code original, j'ai décidé pour des raisons de simplicité de placer le fichier pytest.ini à la racine du projet dans ma version du code. Ainsi, pour tester ces exemples, vous devrez en faire autant.

Toutefois, vous remarquerez en suivant le lien vers le répo que le fichier pytest.ini n'est pas versionné. Ceci est dû au fait que l'auteur (et moi aussi pour cet exemple) souhaite pouvoir y écrire sa clé d'API "en dur".


pytest.ini


[pytest]
env =
    API_KEY=<YOUR_API_KEY>

1. Tester List Breeds (GET)

Le code ci-dessous teste la méthode list_breeds() en lui passant un payload personnalisé et en vérifiant la réponse attendue.

test_dog_api_core_basic.py


	def test_list_breeds(dog_api):
		"""Unit test to list dog breeds

		Args:
			dog_api (DogAPI): Class object parameter in conftest
		"""

		expected_response = [
			{
				"weight": {"imperial": "50 - 60", "metric": "23 - 27"},
				"height": {"imperial": "25 - 27", "metric": "64 - 69"},
				"id": 2,
				"name": "Afghan Hound",
				"country_code": "AG",
				"bred_for": "Coursing and hunting",
				"breed_group": "Hound",
				"life_span": "10 - 13 years",
				"temperament": "Aloof, Clownish, Dignified, Independent, Happy",
				"origin": "Afghanistan, Iran, Pakistan",
				"reference_image_id": "hMyT4CDXR",
				"image": {
					"id": "hMyT4CDXR",
					"width": 606,
					"height": 380,
					"url": "https://cdn2.thedogapi.com/images/hMyT4CDXR.jpg",
				},
			}
		]

		actual_response = dog_api.list_breeds(
			query_dict={"attach_breed": 1, "page": 1, "limit": 1}
		)

		assert actual_response == expected_response

2. Tester Search Breeds (GET)

Au sein du même fichier, écrire un test pour Search Breeds

test_dog_api_core_basic.py


	def test_search_breeds(dog_api):
		"""Unit test to search dog breeds

		Args:
			dog_api (DogAPI): Class object parameter in conftest
		"""

		expected_response = [
			{
				"weight": {"imperial": "17 - 23", "metric": "8 - 10"},
				"height": {"imperial": "13.5 - 16.5", "metric": "34 - 42"},
				"id": 222,
				"name": "Shiba Inu",
				"bred_for": "Hunting in the mountains of Japan, Alert Watchdog",
				"breed_group": "Non-Sporting",
				"life_span": "12 - 16 years",
				"temperament": "Charming, Fearless, Keen, Alert, Confident, Faithful",
				"reference_image_id": "Zn3IjPX3f",
			}
		]

		actual_response = dog_api.search_breeds(query_str="shiba")

		assert actual_response == expected_response

3. Tester Create Vote (POST)

test_dog_api_core_basic.py


	def test_create_vote(dog_api):
		"""Unit test to vote on dog breeds images

		Args:
			dog_api (DogAPI): Class Object Parameter from conftest.
		"""

		expected_response = {
			"message": "SUCCESS",
			"id": 129143,
			"image_id": "asf2",
			"value": 1,
			"country_code": "AR",
		}

		actual_response = dog_api.create_vote(payload={"image_id": "asf2", "value": 1})

		assert actual_response["message"] == expected_response["message"]

Ces types de tests unitaires vérifient le fonctionnement de chaque méthode et s'assurent qu'elles font ce qu'elles disent faire.

Notez l'utilisation de conftest.py pour définir une instance de la classe DogAPI comme fixture.

Test d’authentification

Que se passe-t-il si notre variable d'environnement (et donc notre clé d'API) est perdue ?

Notre connexion à l'API ne fonctionnera pas. Par conséquent, nous avons besoin d'être notifié et de consulter les logs.

Voici ainsi une exemple rapide de test qu'on peut faire pour vérifier que l'API a le comportement attendu lorsque les données d'authentification sont erronées.

test_dog_api_core_basic.py


	def test_dog_api_core_key_error():
		"""
		Unit test to test authentication on POST request
		"""

		dog_api_temp = DogAPI()
		dog_api_temp.headers = {"Content-Type": "application/json", "x-api-key": "FAKE_KEY"}
		expected_response = {"ERROR": "Authorization Error. Please check API key"}

		actual_response = dog_api_temp.create_vote(payload={"image_id": "asf2", "value": 1})

		assert json.loads(actual_response) == expected_response

Dans ce test, on surcharge l'objet de classe DogAPI avec un header temporaire contenant une fausse clé d'API. On s'attend ainsi à ce que la connexion à l'API échoue et que l'API retourne un code 401. Ce test vérifie donc que la réponse qu'on reçoit de l'API est bien le message d'erreur prévu pour l'échec d'authentification.

Test de gestion d’erreurs

Est-ce que votre code peut gérer les erreurs en provenance du serveur ?

Par exemple les erreurs de payload, les erreurs de limite d'appel de l'API ou encore l'expiration de la pagination ?

Tester tous ces cas de figure est important. En effet, lorsque ces cas de figure se produisent, vous pourriez avoir envie de les traiter d'une manière particulière ou avoir à prendre certaines décisions.

Prenons le cas où vous voudriez réduire le nombre de requêtes par seconde que vous faites à l'API externe si vous vous apercevez que celle-ci ne peut pas gérer le nombre de requêtes actuel.

test_dog_api_core_basic.py


	def test_list_breeds_wrong_arg_type_value_error(dog_api):
		with pytest.raises(ValueError):
			dog_api.list_breeds(query_dict="Invalid")


	def test_create_vote_wrong_arg_type_value_error(dog_api):
		with pytest.raises(ValueError):
			dog_api.create_vote(payload="Invalid")


	def test_create_vote_wrong_payload_value_error(dog_api):
		with pytest.raises(ValueError):
			dog_api.create_vote(payload={"image_id": "xyz"})

Test de schémas

Un des plus gros problèmes qui peuvent se produire quand on utilise une API est le changement de schéma.

Disons par exemple que l'API a sorti une nouvelle version et que dans celle-ci, le schéma des réponses est différent. De votre côté, vous n'étiez pas au courant de la sortie de cette nouvelle version.

De plus, dans l'hypothèse où ça arriverait, on ne peut pas toujours faire confiance aux APIs tierces pour honorer la rétro-compatibilité.

Pour toutes ces raisons, tester les changements de schéma et la rétro-compatibilité, et en être informé le plus tôt possible est extrêmement important.

Test de vitesse et de run time

Quel est le temps de réponse moyen ou de latence de l'API tierce ?

Est-ce qu'il est subitement passé de 100 ms à 3000 ms à cause d'une nouvelle release ou de problèmes avec leur serveur ?

Dans ces conditions, cela pourrait avoir un énorme impact sur votre application et gêner les utilisateurs.

C'est donc vital que vous soyiez informé de la situation et que vous y remédiez avant que vos utilisateurs ne recontrent le problème. Par conséquent, des tests de vitesse et de run time sont nécessaires pour anticiper ces problèmes.

Faut-il appeler la vraie API ?

Bien qu'inclure une suite de tests est critique pour que votre application fonctionne correctement, que se passerait-il si vous aviez besoin de lancer ces tests tout le temps ?

Disons par exemple que vous avez un environnement qui lance les tests toutes les heures pour s'assurer que l'API fonctionne comme attendu et que le service est marqué comme healthy.

Cela signifie que vous allez très rapidement atteindre la limite d'appel de l'API, ce qui vous coûtera par conséquent plus d'argent et vous fera gaspiller de précieuses ressources.

Qui plus est, lorsqu'on lance des tests unitaires, on veut avoir le contrôle total de son état et réduire sa dépendance à des services externes.

Pour toutes ces raisons, comme vous l'aurez probablement déduit, appeler l'API externe TOUT le temps est une mauvaise idée.

Mais alors, quelle est la bonne chose à faire ?

Une solution hybride.

On pourrait par exemple faire un mock de la réponse de l'API externe grâce à des librairies Python comme pytest-mock ou requests-mock. Ceci est d'autant plus intéressant si on souhaite lancer nos tests depuis une pipeline de CI/CD.

D'une façon générale, lancer des tests unitaires dans une pipeline CI/CD, en particulier lorsqu'on déploie en production, est bénéfique.

Essentiellement, vous vous attendez à ce que vos tests passent pour ensuite déployer votre application dans l'environnement de production.

Mais pour des tests en local ou répétés en masse, on préférera mocker.

Bien, maintenant, regardons comment faire.

Faire un mock de la réponse à la place

Comment peut-on simuler la réponse de l'API ? De la même façon que les pilotes s'entraînent dans un simulateur ou que les internes en médecine s'entraînent sur des patients mannequin ?

En fait, nous allons le faire par l'intermédiaire de mocks et de patchs.

Mocker ou patcher est une pratique qui consiste à dire aux tests unitaires de retourner une certaine valeur pour une variable, fonction ou instanciation d'objet.

Regardons comment le faire avec du code pour l'illustrer.


Note du traducteur
Les mocks dont nous parlerons ici sont plus exactement ce qu'on appelle des stubs. Pour faire simple, un stub est un remplacement d'une dépendance du code qu'on aimerait pouvoir contrôler, ce qui est exactement ce qu'on fait dans les exemples qui font suivre.


test_dog_api_core_mock.py


	def test_mock_list_breeds(mocker, dog_api):
		mocked_call_api_value = [
			{
				"weight": {"imperial": "44 - 66", "metric": "20 - 30"},
				"height": {"imperial": "30", "metric": "76"},
				"id": 3,
				"name": "African Hunting Dog",
				"bred_for": "A wild pack animal",
				"life_span": "11 years",
				"temperament": "Wild, Hardworking, Dutiful",
				"origin": "",
				"reference_image_id": "rkiByec47",
				"image": {
					"id": "rkiByec47",
					"width": 500,
					"height": 335,
					"url": "https://cdn2.thedogapi.com/images/rkiByec47.jpg",
				},
			}
		]
		mocker.patch("dog_api.core.DogAPI.call_api", return_value=mocked_call_api_value)

		actual_mock_response = dog_api.list_breeds(
			query_dict={"attach_breed": 1, "page": 2, "limit": 1}
		)

		assert mocked_call_api_value == actual_mock_response


	def test_mock_search_breeds(mocker, dog_api):
		mocked_call_api_value = [
			{
				"weight": {"imperial": "13 - 17", "metric": "6 - 8"},
				"height": {"imperial": "13 - 14", "metric": "33 - 36"},
				"id": 139,
				"name": "Jack Russell Terrier",
				"bred_for": "Fox hunting",
				"breed_group": "Terrier",
				"life_span": "12 - 14 years",
			}
		]
		mocker.patch("dog_api.core.DogAPI.call_api", return_value=mocked_call_api_value)

		actual_mock_response = dog_api.search_breeds(query_str="Jack Russell Terrier")

		assert mocked_call_api_value == actual_mock_response


	def test_mock_create_vote(mocker, dog_api):
		mocked_call_api_value = {
			"message": "SUCCESS",
			"id": 129143,
			"image_id": "asf2",
			"value": 1,
			"country_code": "AR",
		}
		mocker.patch("dog_api.core.DogAPI.call_api", return_value=mocked_call_api_value)

		actual_mock_response = dog_api.create_vote({"image_id": "asf2", "value": 1})

		assert mocked_call_api_value == actual_mock_response

Dans cet exemple, nous allons utiliser la librarie pytest-mock, qui est un wrapper autour de l'API de patching fournie par le package mock.

Si vous suivez le code source, vous verrez que les méthodes d'API list_breeds(), search_breeds() et create_vote() utilisent la méthode call_api().

La fonction call_api() implémente en effet quelques conditions logiques pour exécuter une requête GET ou PUT. Mais on ne s'intéresse pas vraiment à comment elle fonctionne ici.

Par contre, ce qui nous intéresse réellement ici est la réponse des méthodes list_breeds(), search_breeds() et create_vote().

Alors, on fait un mock de la réponse de call_api(). En d'autres termes, on "truque" sa réponse.

Cela nous aide à tester le fonctionnement des méthodes qui nous intéressent sans s'occuper du comportement de l'API externe.

On peut aller plus loin en faisant un mock de variable ou en faisant un mock de l'instanciation d'une classe.

Cet article de Chang Hsin Lee explique bien comment utiliser pytest-mock dans le détail.

Mais avant toute chose, vous vous demandez sûrement quels avantages il y a à faire un mock ?

En voici quelques uns :

Les avantages du mocking/patching

  • Mocker nous évite d'appeler à répétition l'API externe pour tester une fonctionnalité
  • Le temps d'exécution de tests est fortement réduit
  • La prédictibilité : les réponses étant prévisibles (vu qu'elles sont "truquées"), on peut écrire des assertions exactes et garantir la réponse

Les problèmes avec le mocking

Comme toute chose dans la vie, faire des mocks a des pour et des contre.

En effet, bien que faire un mock est en soit une bonne chose pour différentes raisons, il y a d'autres éléments à prendre en compte avant de décider d'utiliser des mocks dans vos tests unitaires :

  • En cas de modification du fonctionnement de l'API externe, les tests utilisant des mocks ne nous permettent pas de nous en rendre compte
  • Couplage fort - Les tests sont fortement couplés aux détails d'implémentation, en particulier comment on importe des fichiers et des modules ainsi que notre structure de code
  • Il faut se souvenir de patcher à chaque test
  • Les mocks mélangent la logique business et les détails techniques
  • Les tests sont plus "moches" et moins lisibles

Note du traducteur (je vous ai manqué ? 😉 )
Comme tout juste mentionné, l'API externe peut avoir (beaucoup) changé entre temps. C'est l'un des plus gros problèmes avec les mocks parce qu'on aura l'illusion que tous nos tests sont OK alors qu'en production les choses vont mal tourner.

C'est pour cette raison qu'on recommande de ne pas faire que des tests avec des mocks mais de pencher plutôt pour une approche hybride (parce que malgré tout, les mocks ont des avantages).


Quelle est donc la meilleure solution ?

Comme mentionné précédemment, une approche hybride est préférée car elle permet de gagner en fiabilité. Par exemple, les tests de schéma nous informent tout de suite d'éventuels changements sur la structure des données renvoyées par l'API externe.

En ce qui concerne le mocking, il existe des alternatives qui nous permettent elles aussi de ne pas dépendre de l'API externe tout en préservant la logique business et l'élégance du code.

Dans un prochain article, on apprendra comment utiliser des adapteurs et des librairies comme VCR.py pour résoudre certains des problèmes précédemment mentionnés.

Cette vidéo de Harry Percival explique certains des problèmes précédents tout en proposant des solutions.


Note du traducteur
Nous aborderons également ces sujets au cours de l'année sur le blog d'Arolla. Restez vigilants !


Conclusion

Merci d'avoir suivi cet article jusqu'au bout. J'espère qu'il vous aura été utile.

Vous êtes venu ici avec l'intention d'apprendre comment tester unitairement du code faisant appel à une API REST et nous avons couvert beaucoup de sujets incluant

  • Quelques fondamentaux sur les API REST
  • Un exemple d'utilisation d'une API REST dans un code Python
  • Les types de test à couvrir
  • Mocker et patcher (avantages et inconvénients)

Par l'intermédiaire de ces techniques, vous allez pouvoir écrire du code plus robuste et apte pour un déploiement en production tout en renforçant votre confiance dans le fait de réaliser un bon produit.

Pour aller plus loin, certains autres sujets, qui seront abordés dans le futur, sont tout aussi importants :

  1. Utiliser des adapteurs et VCR.py comme alternative au mocking
  2. Gérer les différents statuts HTTP d'une API externe
  3. Lancer les tests unitaires dans une pipeline de CI/CD
  4. Couverture de plus de méthodes d'API et de variation de tests pour l'API Dog
  5. Tester plusieurs types d'authentification

A bientôt pour un prochain article !


Note finale du traducteur
A bientôt tout le monde 🙂


Consultant Python/Data à Arolla | Plus de publications

Comments are closed.