Fixtures Pytest

Terminé

Les fixtures sont des fonctions d’assistance pytest utilisées pour créer des tests modulaires, évolutifs et maintenables. Vous utilisez des appareils pour configurer des conditions préalables pour les tests tels que les connexions de base de données, la création de données de test ou la configuration d’un état système requis pour qu’un test puisse s’exécuter. Elles peuvent également être utilisées pour le nettoyage après l’exécution des tests.

Les principales caractéristiques des appareils pytest sont les suivantes :

  • Contrôle d’étendue : les fixtures peuvent être configurées pour avoir différentes étendues à l’aide du paramètre scope (tel que function, class, module ou session), qui détermine la fréquence à laquelle le dispositif est appelé.
  • Gestion de l’installation et de la destruction : Pytest gère le cycle de vie des appareils, la configuration et la suppression automatiques en fonction des besoins.
  • Injection de dépendances : les fixtures sont injectées dans des fonctions de test en tant qu'arguments, ce qui permet de déterminer clairement quels tests s'appuient sur quels fixtures.
  • Réutilisation et modularité : les appareils peuvent être définis à un seul endroit et utilisés dans plusieurs fonctions de test, modules ou même projets.

Créer une fixture de fichier temporaire

Lors de l’écriture de tests qui interagissent avec des fichiers, il est courant d’avoir besoin de fichiers temporaires qui n’encombrent pas le système de fichiers après le test. Avec pytest, nous pouvons créer un dispositif qui configure un fichier temporaire. Le dispositif utilise le module de tempfile Python pour générer des fichiers temporaires en toute sécurité, garantissant qu'ils peuvent être utilisés et supprimés sans affecter l'environnement local. (Dans cette version initiale de notre appareil, le fichier ne sera pas automatiquement supprimé en raison de l’indicateur delete=False. Nous traiterons la suppression du fichier plus tard.)

Voici à quoi ressemble l’appareil :

import pytest
import tempfile

@pytest.fixture
def tmp_file():
    def create():
        # Create a named temporary file that persists beyond the function scope
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

Dans cette configuration, tmp_file() agit en tant que fixture. Le nom de la fixture correspond à la façon dont les tests la référencent. Au sein du dispositif, la fonction create() imbriquée crée le fichier uniquement lorsqu’elle est appelée, plutôt que lors de la configuration des appareils. Cela permet un contrôle précis sur le moment où le fichier temporaire est créé, ce qui est utile dans les tests où le minutage et l’état du fichier sont critiques.

Dans la fonction create()imbriquée, un fichier temporaire est créé, puis retourne le chemin absolu vers celui-ci. Voici un exemple de la façon dont un test peut utiliser le dispositif que nous avons écrit :

import os

def test_file(tmp_file):
    path = tmp_file()
    assert os.path.exists(path)

Un test utilise un appareil en spécifiant le nom du dispositif en tant qu’argument. Notre cas d’usage simple peut être facilement développé en écrivant dans le fichier ou en apportant des modifications telles que la modification des autorisations ou de la propriété.

Gestion de l’étendue

Dans pytest, la gestion du cycle de vie des ressources de test par le biais de routines d’installation et de suppression est essentielle pour maintenir des environnements de test propres et efficaces. Vous souhaitez également protéger l’intégrité des tests en vous assurant que chaque test commence par un état connu et cohérent. Par défaut, les fixtures pytest fonctionnent avec un function périmètre, ce qui influence le comportement de deux manières :

  • Cycle de vie par test : la valeur de retour du dispositif est recalculée pour chaque fonction de test qui l’utilise, ce qui garantit que chaque test fonctionne avec un nouvel état.
  • Nettoyage après chaque utilisation : toutes les opérations de nettoyage nécessaires sont effectuées après chaque test qui utilise le dispositif.

Pytest permet également aux fixtures d’être étendues plus largement pour optimiser les performances et l’utilisation des ressources. Le scoping est particulièrement utile dans des situations telles que la gestion de l’état de la base de données ou lorsque vous avez des configurations d’état complexes qui prennent du temps à mettre en place. Les quatre étendues disponibles sont les suivantes :

  • function : l’étendue par défaut, la fixture est exécutée une fois par test.
  • class: le dispositif s’exécute une fois par classe de test.
  • module : s’exécute une fois pour un module.
  • session: s’exécute une fois par session de test. Cette étendue est utile pour les opérations coûteuses qui doivent être conservées tout au long de la session de test, telles que l’initialisation d’un service ou le démarrage d’un serveur de base de données.

Dans ce cas, l’exécution une fois signifie que la valeur de retour est mise en cache. Ainsi, une fonction qui a une portée de « module » peut être appelée plusieurs fois dans un module de test, mais la valeur de retour est celle du premier test qui l'a appelée.

Voici à quoi ressemblerait la fixture tmp_file() avec une étendue de module :

import pytest
import tempfile

@pytest.fixture(scope="module")
def tmp_file():
    def create():
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

Gestion du nettoyage

Le code précédent spécifiant le tmp_file dispositif crée un fichier temporaire, mais il ne gère pas automatiquement le nettoyage une fois les tests terminés. Pour vous assurer que les fichiers temporaires ne sont pas laissés derrière vous, vous pouvez utiliser le dispositif pytest request pour inscrire une fonction de nettoyage.

Voici comment vous pouvez modifier le dispositif tmp_file pour inclure le nettoyage automatique :

import pytest
import tempfile
import os

@pytest.fixture(scope="module")
def tmp_file(request):
    # Create a temporary file that persists beyond the function scope
    temp = tempfile.NamedTemporaryFile(delete=False)

    def create():
        # Returns the path of the temporary file
        return temp.name

    def cleanup():
        # Remove the file after the tests are done
        os.remove(temp.name)

    # Register the cleanup function to be called after the last test in the module
    request.addfinalizer(cleanup)

    return create

En utilisant request.addfinalizer() et en passant la fonction imbriquée cleanup(), le nettoyage est appelé en fonction de l’étendue. Dans ce cas-ci, l’étendue est module, donc après tous les tests d’un module, pytest appelle cette fonction de nettoyage.

Utilisation de conftest.py

Au lieu d’inclure vos éléments dans vos fichiers de test, vous pouvez les enregistrer dans un fichier conftest.py . Tous les éléments de conftest.py sont automatiquement disponibles pour vos tests dans le même répertoire sans avoir à les importer explicitement.

Explorer les accessoires intégrés

Pytest a de nombreux appareils intégrés conçus pour simplifier les tests. Ces appareils peuvent gérer automatiquement l’installation et le nettoyage, ce qui vous permet de vous concentrer sur l’écriture de vos cas de test au lieu de la gestion des tests.

Les éléments intégrés clés sont les suivants :

  • cache: permet de créer et de gérer le cache au niveau du test, ce qui est utile pour stocker des données entre les sessions de test.
  • capsys: capture et permet l'inspection de stderr et stdout, facilitant ainsi l'inspection et le test des sorties de console.
  • tmpdir: fournit un répertoire temporaire pour les fichiers qui doivent être créés et utilisés pendant les tests.
  • monkeypatch: permet de modifier en toute sécurité le comportement et les valeurs des objets, fonctions et environnement de votre système d’exploitation.

Rôle du monkeypatching dans les tests

Le test du code qui s’intègre étroitement à des ressources externes telles que des bases de données ou des API externes peut être difficile en raison des dépendances impliquées. Une technique appelée monkey patching consiste à modifier temporairement votre système pendant les exécutions de test, permettant ainsi une indépendance vis-à-vis des systèmes externes et vous permettant de modifier en toute sécurité l'état et le comportement de votre environnement système pendant les tests.

Voici un exemple de remplacement de la fonction os.path.exists() à l’aide de la fonction monkeypatch :

import os

def test_os(monkeypatch):
    # Override os.path.exists to always return False
    monkeypatch.setattr('os.path.exists', lambda x: False)
    assert not os.path.exists('/')

Vous pouvez également utiliser la setattr() méthode avec une référence directe à l’objet et à l’attribut :

def test_os(monkeypatch):
    # Specify the object and attribute to override
    monkeypatch.setattr(os.path, 'exists', lambda x: False)
    assert not os.path.exists('/')

Outre la définition d’attributs et la substitution de méthodes, le monkeypatch dispositif peut définir et supprimer des variables d’environnement, modifier des valeurs de dictionnaire et modifier les chemins système. Le monkeypatch dispositif rétablit automatiquement toutes les modifications après chaque test, mais vous devez toujours prendre soin lors de l’utilisation du monkeypatch dispositif. Voici quelques raisons d’être prudent lors de son utilisation :

-Clarté et maintenance du code : l’utilisation monkeypatch excessive ou l’utilisation de celui-ci de manière complexe peut rendre les tests plus difficiles à comprendre et à gérer. Lorsque vous lisez vos résultats de test, il se peut qu’il ne soit pas immédiatement clair comment les composants sont censés se comporter normalement et comment ils sont modifiés pour les tests.-Validité des tests : Monkeypatching peut parfois entraîner des tests qui réussissent dans des conditions artificielles très différentes de l’environnement de production. Cela peut créer un faux sentiment de sécurité, car les tests peuvent réussir parce que le test a modifié le comportement du système de manière excessive.-Surdépendence sur les détails de l’implémentation : les tests qui s’appuient sur monkeypatching peuvent être étroitement couplés à des détails d’implémentation spécifiques du code qu’ils testent. Cela peut rendre les tests fragiles et sensibles à la rupture avec même des modifications mineures apportées à la base de code sous-jacente.-Complexité du débogage : les tests de débogage qui utilisent monkeypatch peuvent être plus complexes, en particulier si le correctif modifie les aspects fondamentaux du comportement des applications. Comprendre pourquoi un test échoue peut nécessiter une présentation plus approfondie de la façon dont les composants sont modifiés pendant le test.

Bien qu’il monkeypatch s’agisse d’un outil puissant pour créer des environnements de test isolés et contrôlés, il doit être utilisé judicieusement et avec une compréhension claire de la façon dont elle affecte la suite de tests et le comportement de l’application.