4 techniques pour tester les applications de ligne de commande Python (CLI)

4 techniques pour tester les applications de ligne de commande Python (CLI)

Vous venez de terminer la création de votre première application en ligne de commande Python. Ou peut-être votre deuxième ou troisième. Vous apprenez Python depuis un certain temps, et maintenant vous êtes prêt à créer something plus grand et plus complexe , mais toujours exécutable sur une ligne de commande. Ou vous avez l’habitude de building and testing des applications Web ou des applications de bureau avec une interface graphique, mais vous commencez maintenant à créer des applications CLI.

Dans toutes ces situations et plus encore, vous devrez apprendre et vous familiariser avec les différentes méthodes de test d’une application CLI Python.

Bien que les choix d’outils puissent être intimidants, la principale chose à garder à l’esprit est que vous comparez simplement les sorties que votre code génère aux sorties que vous attendez. Tout en découle.

*Dans ce didacticiel, vous apprendrez quatre techniques pratiques pour tester les applications en ligne de commande Python:*
  • Débogage «Lo-Fi» avec + print () +

  • Utilisation d’un débogueur visuel Python

  • Tests unitaires avec pytest et simulations

  • Test d’intégration

    *Bonus gratuit:* lien: # [Cliquez ici pour obtenir notre aide-mémoire de test Python] qui résume les techniques présentées dans ce didacticiel.

Tout sera structuré autour d’une application CLI Python de base qui transmet les données sous la forme d’un dictionnaire à plusieurs niveaux à deux fonctions qui les transforment en quelque sorte, puis les imprime à l’utilisateur.

Nous utiliserons le code ci-dessous pour examiner quelques-unes des différentes méthodes qui vous aideront dans les tests. Et bien qu’il ne soit certainement pas exhaustif, j’espère que ce didacticiel vous donnera suffisamment d’étendue pour vous permettre de créer des tests efficaces dans les principaux domaines de test.

J’ai ajouté quelques bogues dans ce code initial, que nous exposerons avec nos méthodes de test.

_ Remarque : Par souci de simplicité, ce code n’inclut pas certaines bonnes pratiques de base, telles que la vérification de l’existence de clés dans un dictionnaire. _

Dans un premier temps, réfléchissons à nos objets à chaque étape de cette application. Nous commençons avec une structure qui décrit John Q. Publique:

JOHN_DATA = {
    'name': 'John Q. Public',
    'street': '123 Main St.',
    'city': 'Anytown',
    'state': 'FL',
    'zip': 99999,
    'relationships': {
        'siblings': ['Michael R. Public', 'Suzy Q. Public'],
        'parents': ['John Q. Public Sr.', 'Mary S. Public'],
    }
}

Nous aplatissons ensuite les autres dictionnaires, en attendant cela après avoir appelé notre première fonction de transformation, + initial_transform +:

JOHN_DATA = {
    'name': 'John Q. Public',
    'street': '123 Main St.',
    'city': 'Anytown',
    'state': 'FL',
    'zip': 99999,
    'siblings': ['Michael R. Public', 'Suzy Q. Public'],
    'parents': ['John Q. Public Sr.', 'Mary S. Public'],
}

Ensuite, nous construisons toutes les informations d’adresse dans une seule entrée d’adresse avec la fonction + final_transform +:

JOHN_DATA = {
    'name': 'John Q. Public',
    'address': '123 Main St. \nAnytown, FL 99999'
    'siblings': ['Michael R. Public', 'Suzy Q. Public'],
    'parents': ['John Q. Public Sr.', 'Mary S. Public'],
}

Et l’appel à + ​​print_person + écrira ceci sur la console:

Hello, my name is John Q. Public, my siblings are Michael R. Public
and Suzy Q. Public, my parents are John Q. Public Sr. and Mary S. Public,
and my mailing address is:
123 Main St.
Anytown, FL 99999

testapp.py:

def initial_transform(data):
    """
    Flatten nested dicts
    """
    for item in list(data):
        if type(item) is dict:
            for key in item:
                data[key] = item[key]

    return data


def final_transform(transformed_data):
    """
    Transform address structures into a single structure
    """
    transformed_data['address'] = str.format(
        "{0}\n{1}, {2} {3}", transformed_data['street'],
        transformed_data['state'], transformed_data['city'],
        transformed_data['zip'])

    return transformed_data


def print_person(person_data):
    parents = "and".join(person_data['parents'])
    siblings = "and".join(person_data['siblings'])
    person_string = str.format(
        "Hello, my name is {0}, my siblings are {1}, "
        "my parents are {2}, and my mailing"
        "address is: \n{3}", person_data['name'],
        parents, siblings, person_data['address'])
    print(person_string)


john_data = {
    'name': 'John Q. Public',
    'street': '123 Main St.',
    'city': 'Anytown',
    'state': 'FL',
    'zip': 99999,
    'relationships': {
        'siblings': ['Michael R. Public', 'Suzy Q. Public'],
        'parents': ['John Q. Public Sr.', 'Mary S. Public'],
    }
}

suzy_data = {
    'name': 'Suzy Q. Public',
    'street': '456 Broadway',
    'apt': '333',
    'city': 'Miami',
    'state': 'FL',
    'zip': 33333,
    'relationships': {
        'siblings': ['John Q. Public', 'Michael R. Public',
                    'Thomas Z. Public'],
        'parents': ['John Q. Public Sr.', 'Mary S. Public'],
    }
}

inputs = [john_data, suzy_data]

for input_structure in inputs:
    initial_transformed = initial_transform(input_structure)
    final_transformed = final_transform(initial_transformed)
    print_person(final_transformed)

Pour le moment, le code ne répond pas réellement à ces attentes, nous allons donc étudier l’utilisation des quatre techniques pendant que nous les apprenons. En faisant cela, vous obtiendrez une expérience pratique de l’utilisation de ces techniques, élargissez votre zone de confort et commencez à apprendre pour quels problèmes elles conviennent le mieux.

Débogage «Lo-Fi» avec impression

C’est l’un des moyens les plus simples de tester. Tout ce que vous avez à faire ici est «+ imprimer +» une variable ou un objet qui vous intéresse - avant un appel de fonction, après un appel de fonction ou dans une fonction.

Respectivement, ceux-ci vous permettent de vérifier l’entrée d’une fonction, la sortie d’une fonction et la logique d’une fonction.

Si vous enregistrez le code ci-dessus sous + testapp.py + et essayez de l’exécuter avec + python testapp.py +, vous verrez une erreur comme ceci:

Traceback (most recent call last):
  File "testapp.py", line 60, in <module>
    print_person(final_transformed)
  File "testapp.py", line 23, in print_person
    parents = "and".join(person_data['parents'])
KeyError: 'parents'

Il y a une clé manquante dans + person_data + qui est passée dans + print_person +. La première étape serait de vérifier l’entrée vers + print_person + et de voir pourquoi notre sortie attendue (un message imprimé) n’est pas générée. Nous allons simplement ajouter un appel de fonction + print + avant l’appel à + ​​print_person +:

final_transformed = final_transform(initial_transformed)
print(final_transformed)
print_person(final_transformed)

La fonction + print + fait le travail ici, montrant dans sa sortie que nous n’avons pas la clé de haut niveau + parents + - ni la touche + frères et sœurs + - mais dans l’intérêt de notre raison, je vais vous montre + pprint +, qui imprime des objets à plusieurs niveaux d’une manière plus lisible. Pour l’utiliser, ajoutez + à partir de pprint import pprint + en haut de votre script.

Au lieu de + print (final_transformed) +, nous appelons + pprint (final_transformed) + pour inspecter notre objet:

{'address': '123 Main St.\nFL, Anytown 99999',
 'city': 'Anytown',
 'name': 'John Q. Public',
 'relationships': {'parents': ['John Q. Public Sr.', 'Mary S. Public'],
                   'siblings': ['Michael R. Public', 'Suzy Q. Public']},
 'state': 'FL',
 'street': '123 Main St.',
 'zip': 99999}

Comparez cela avec le formulaire final attendu ci-dessus.

Parce que nous savons que + final_transform + ne touche pas le dictionnaire + relations +, il est temps de voir ce qui se passe dans + initial_transform +. Normalement, j’utilise un débogueur traditionnel pour passer à travers cela, mais je veux vous montrer une autre utilisation du débogage d’impression.

Nous pouvons imprimer l’état des objets dans le code, mais nous ne sommes pas limités à cela. Nous pouvons imprimer ce que nous voulons, nous pouvons donc également imprimer des marqueurs pour voir quelles branches logiques sont exécutées et quand.

Parce que + initial_transform + est principalement composé de quelques boucles, et parce que les dictionnaires internes sont censés être gérés par la boucle for interne, nous devons vérifier ce qui s’y passe, le cas échéant:

def initial_transform(data):
    """
    Flatten nested dicts
    """
    for item in list(data):
        if type(item) is dict:
            print "item is dict!"
            pprint(item)
            for key in item:
                data[key] = item[key]

    return data

Si nous rencontrons un dictionnaire dans notre entrée + data +, alors nous serons alertés dans la console et ensuite nous verrons à quoi ressemble l’élément.

Après l’exécution, la sortie de notre console n’a pas changé. C’est une bonne preuve que notre déclaration "+ if +" ne fonctionne pas comme prévu. Bien que nous puissions continuer à imprimer pour trouver le bogue, c’est un excellent moyen de démontrer les points forts de l’utilisation d’un débogueur.

Comme exercice, cependant, je recommande de rechercher des bogues dans ce code en utilisant uniquement le débogage d’impression. C’est une bonne pratique et vous obligera à réfléchir à toutes les façons d’utiliser la console pour vous alerter des différentes choses qui se produisent dans le code.

Emballer

*Quand utiliser le débogage d'impression:*
  • Objets simples

  • Scripts plus courts

  • Bogues apparemment simples

  • Inspections rapides

    *Plongez plus profondément:*
  • pprint - embellir les objets imprimés

Avantages:

  • Test rapide

  • Facile à utiliser

Les inconvénients:

  • Dans la plupart des cas, vous devez exécuter l’ensemble du programme, sinon:

  • Vous devez ajouter du code supplémentaire pour contrôler manuellement le flux

  • Vous pouvez accidentellement laisser le code de test une fois terminé, en particulier dans le code complexe

Utilisation d’un débogueur

Les débogueurs sont parfaits lorsque vous souhaitez parcourir le code une ligne à la fois et inspecter l’ensemble de l’état de l’application. Ils vous aident lorsque vous savez à peu près où se produisent les erreurs, mais vous ne savez pas pourquoi, et ils vous donnent une belle vue de haut en bas de tout ce qui se passe dans votre application à la fois.

Il existe de nombreux débogueurs, et ils sont souvent fournis avec des IDE. Python possède également a un module appelé + pdb + qui peut être utilisé dans REPL pour code de débogage. Plutôt que d’entrer dans les détails spécifiques à l’implémentation de tous les débogueurs disponibles, dans cette section, je vais vous montrer comment utiliser les débogueurs avec des fonctions communes, telles que la définition de breakpoints et watches.

*Les points d'arrêt* sont des marqueurs sur votre code qui indiquent à votre débogueur où suspendre l'exécution pour que vous puissiez inspecter l'état de votre application. *Les montres* sont des expressions que vous pouvez ajouter pendant une session de débogage pour surveiller la valeur d'une variable (et plus) et qui sont conservées pendant l'exécution de votre application.

Mais revenons aux points d’arrêt. Ceux-ci seront ajoutés là où vous souhaitez démarrer ou continuer une session de débogage. Puisque nous déboguons la méthode + initial_transform +, nous voudrons en mettre une là. Je dénoterai le point d’arrêt avec un + (*) +:

def initial_transform(data):
    """
    Flatten nested dicts
    """
(*) for item in list(data):
        if type(item) is dict:
            for key in item:
                data[key] = item[key]

    return data

Désormais, lorsque nous commencerons le débogage, l’exécution s’arrêtera sur cette ligne et vous pourrez voir les variables et leurs types à ce stade particulier de l’exécution du programme. Nous avons quelques options pour naviguer dans notre code: step over, step in et step out sont les plus courantes.

Etape over est celui que vous utiliserez le plus souvent - cela passe simplement à la ligne de code suivante.

Etp in, tente d’approfondir le code. Vous l’utiliserez lorsque vous rencontrerez un appel de fonction que vous souhaitez étudier plus en profondeur. Vous serez directement dirigé vers le code de cette fonction et pourrez en examiner l’état. Vous l’utilisez également souvent lorsque vous le confondez avec step over. Heureusement, la sortie peut nous sauver, cela nous ramène à l’appelant.

Nous pouvons également définir une watch ici, quelque chose comme + type (item) is dict + ', ce que vous pouvez faire dans la plupart des IDE via un bouton" ajouter une montre "pendant une session de débogage. Cela affichera désormais `+ True + ou + False +, peu importe où vous vous trouvez dans le code.

Réglez la montre et passez maintenant au-dessus de sorte que vous êtes maintenant en pause sur la ligne + si le type (élément) est dict: +. Vous devriez maintenant pouvoir voir l’état de la montre, la nouvelle variable + item +, et l’objet + data +.

Python Débogueur Capture d’écran: Variables d’observation

Même sans la montre, nous pouvons voir le problème: plutôt que + type + regardant ce que + item + pointe, il regarde le type de + item + lui-même, qui est une chaîne. Les ordinateurs font exactement ce que nous leur disons, après tout. Grâce au débogueur, nous voyons l’erreur de nos façons et fixons notre code comme ceci:

def initial_transform(data):
    """
    Flatten nested dicts
    """
    for item in list(data):
        if type(data[item]) is dict:
            for key in data[item]:
                data[key] = item[key]

    return data

Nous devons l’exécuter à nouveau dans le débogueur et nous assurer que le code va là où nous l’attendons. Et nous ne le sommes pas, la structure ressemble maintenant à ceci:

john_data = {
    'name': 'John Q. Public',
    'street': '123 Main St.',
    'city': 'Anytown',
    'state': 'FL',
    'zip': 99999,
    'relationships': {
        'siblings': ['Michael R. Public', 'Suzy Q. Public'],
        'parents': ['John Q. Public Sr.', 'Mary S. Public'],
    },
    'siblings',
    'parents',
}

Maintenant que nous avons vu comment un débogueur visuel est utilisé, approfondissons et mettons nos nouvelles connaissances à l’épreuve en effectuant l’exercice ci-dessous.

Nous avons parlé du débogueur visuel. Nous avons utilisé le débogueur visuel. Nous adorons le débogueur visuel. Il existe cependant des avantages et des inconvénients à cette technique, et vous pouvez les examiner dans la section ci-dessous.

Emballer

*Quand utiliser un débogueur Python:*
  • Projets plus complexes

  • Difficile de détecter les bugs

  • Vous devez inspecter plusieurs objets

  • Vous avez une idée approximative de une erreur se produit, mais vous devez y remédier

    *Plongez plus profondément:*
  • Points d’arrêt conditionnels

  • Évaluation des expressions lors du débogage

Avantages:

  • Contrôle du déroulement du programme

  • Vue à vol d’oiseau de l’état de l’application

  • Pas besoin de savoir exactement où le bug se produit

Les inconvénients:

  • Difficile de regarder manuellement de très gros objets

  • Le code à exécution longue prendra très longtemps à déboguer

Tests unitaires avec Pytest et Mocks

Les techniques précédentes sont fastidieuses et peuvent nécessiter des modifications de code si vous souhaitez tester de manière exhaustive les combinaisons entrée-sortie, en vous assurant de toucher chaque branche de votre code, en particulier à mesure que votre application se développe. Dans notre exemple, la sortie de + initial_transform + ne semble toujours pas correcte.

Bien que la logique de notre code soit assez simple, elle peut facilement augmenter en taille et en complexité, ou devenir la responsabilité de toute une équipe. Comment testons-nous une application de manière plus structurée, détaillée et automatisée?

Entrez les tests unitaires.

Le test unitaire est une technique de test qui décompose le code source en unités reconnaissables (généralement des méthodes ou des fonctions) et les teste individuellement.

Vous allez essentiellement écrire un script ou un groupe de scripts qui testent chaque méthode avec différentes entrées pour garantir que chaque branche logique au sein de chaque méthode est testée - c’est ce qu’on appelle la couverture de code, et généralement vous voulez viser une couverture de code à 100%. Ce n’est pas toujours nécessaire ou pratique, mais nous pouvons l’enregistrer pour un autre article (ou un manuel).

Chaque test traite la méthode testée de manière isolée: les appels externes sont remplacés par une technique appelée mocking pour donner des valeurs de retour fiables et tout objet configuré avant le test est supprimé après le test. Ces techniques et d’autres sont faites pour assurer l’indépendance et l’isolement de l’unité testée.

La répétabilité et l’isolement sont la clé de ce type de tests, même si nous continuons avec notre thème de comparer les sorties attendues aux sorties réelles. Maintenant que vous avez une compréhension globale des tests unitaires, vous pouvez faire un détour rapide et voir comment tester les applications Flask avec la minimum viable test suite .

Pytest

Alors maintenant que nous sommes probablement allés un peu trop loin dans la théorie, voyons comment cela fonctionne dans la pratique. Python est livré avec un module intégré + unittest +, mais je crois que + pytest + fait un excellent travail de construction sur ce que + unittest + fournit. Quoi qu’il en soit, je vais simplement montrer les bases du test unitaire, car le test unitaire peut prendre plusieurs articles longs.

Une convention courante consiste à placer tous vos tests dans un répertoire + test + au sein de votre projet. Comme il s’agit d’un petit script, un fichier + test_testapp.py + au même niveau que + testapp.py + est suffisant.

Nous allons écrire un test unitaire pour + initial_transform + pour montrer comment configurer un ensemble d’entrées et de sorties attendues et nous assurer qu’elles correspondent. Le modèle de base que j’utilise avec + pytest + est de configurer un fixture qui prendra certains paramètres et les utilisera pour générer les entrées de test et attendues sorties que je veux.

Je vais d’abord montrer la configuration du fixture, et pendant que vous regardez le code, pensez aux cas de test dont vous aurez besoin pour frapper toutes les branches possibles de + initial_transform +:

import pytest
import testapp as app

@pytest.fixture(params=['nodict', 'dict'])
def generate_initial_transform_parameters(request):

Avant de générer des entrées, regardons ce qui se passe ici, car cela peut prêter à confusion.

Tout d’abord, nous utilisons le décorateur + @ pytest.fixture + pour déclarer la définition de fonction suivante comme un luminaire. Nous utilisons également un paramètre nommé + params + à utiliser avec + generate_initial_transform_parameters +.

La fonctionnalité intéressante avec cela est que chaque fois que la fonction décorée est utilisée, elle sera utilisée avec chaque paramètre, donc simplement appeler + generate_initial_transform_parameters + l’appellera deux fois, une fois avec + nodict + comme paramètre et une fois avec `+ dict + ".

Pour accéder à ces paramètres, nous ajoutons l’objet spécial pytest + request + à notre signature de fonction.

Construisons maintenant nos entrées et sorties attendues:

@pytest.fixture(params=['nodict', 'dict'])
def generate_initial_transform_parameters(request):
    test_input = {
        'name': 'John Q. Public',
        'street': '123 Main St.',
        'city': 'Anytown',
        'state': 'FL',
        'zip': 99999,
    }
    expected_output = {
        'name': 'John Q. Public',
        'street': '123 Main St.',
        'city': 'Anytown',
        'state': 'FL',
        'zip': 99999,
    }

    if request.param == 'dict':
        test_input['relastionships'] = {
            'siblings': ['Michael R. Public', 'Suzy Q. Public'],
            'parents': ['John Q. Public Sr.', 'Mary S. Public'],
        }
        expected_output['siblings'] = ['Michael R. Public', 'Suzy Q. Public']
        expected_output['parents'] = ['John Q. Public Sr.', 'Mary S. Public']

    return test_input, expected_output

Rien de trop surprenant ici, nous configurons l’entrée et la sortie attendue, et si nous avons le paramètre + 'dict' +, alors nous modifions l’entrée et la sortie attendue, ce qui nous permet de tester le bloc + if +.

Ensuite, nous écrivons le test. Dans le test, nous devons passer le luminaire à la fonction de test en tant que paramètre pour y avoir accès:

def test_initial_transform(generate_initial_transform_parameters):
    test_input = generate_initial_transform_parameters[0]
    expected_output = generate_initial_transform_parameters[1]
    assert app.initial_transform(test_input) == expected_output

Les fonctions de test doivent être précédées de + test_ + et doivent être basées sur https://dbader.org/blog/python-assert-tutorial [déclarations + assert +). Ici, nous affirmons que la sortie que nous obtenons en passant notre entrée à notre fonction réelle est égale à notre sortie attendue. Lorsque vous l’exécutez dans votre IDE avec une configuration de test ou avec + pytest + dans la CLI, vous obtiendrez… des erreurs! Notre sortie n’est pas encore tout à fait correcte. Corrigeons-le à l’aide de l’exercice suivant: l’expérience pratique est inestimable, et la mise en pratique de ce que vous lirez facilitera le rappel à l’avenir.

Se moquer

Les simulations sont une autre partie importante des tests unitaires. Parce que nous ne testons qu’une seule unité de code, nous ne nous soucions pas vraiment de ce que font les autres appels de fonction. Nous voulons juste avoir un retour fiable de leur part.

Ajoutons un appel de fonction externe à + ​​initial_transform +:

def initial_transform(data):
    """
    Flatten nested dicts
    """
    for item in list(data):
        if type(data[item]) is dict:
            for key in data[item]:
                data[key] = data[item][key]
            data.pop(item)

    outside_module.do_something()
    return data

Nous ne voulons pas faire d’appels en direct à + ​​do_something () +, nous allons donc faire une maquette dans notre script de test. Le simulateur interceptera cet appel et renverra tout ce que vous aurez configuré pour le simuler. J’aime configurer les simulations dans les appareils, car cela fait partie de la configuration de test et nous pouvons garder tout ce code de configuration ensemble:

@pytest.fixture(params=['nodict', 'dict'])
def generate_initial_transform_parameters(request, mocker):
    [...]
    mocker.patch.object(outside_module, 'do_something')
    mocker.do_something.return_value(1)
    [...]

Maintenant, chaque fois que vous appelez + initial_transform +, l’appel + do_something + sera intercepté et renverra 1. Vous pouvez également profiter des paramètres de fixture pour déterminer ce que votre maquette renvoie - ceci est important lorsqu’une branche de code est déterminée par le résultat de l’appel extérieur.

Une dernière astuce intéressante consiste à utiliser + side_effect +. Entre autres choses, cela vous permet de simuler différents retours pour des appels successifs à la même fonction:

def initial_transform(data):
    """
    Flatten nested dicts
    """
    for item in list(data):
        if type(data[item]) is dict:
            for key in data[item]:
                data[key] = data[item][key]
            data.pop(item)

    outside_module.do_something()
    outside_module.do_something()
    return data

Nous avons configuré notre maquette de cette manière, avec une liste de sorties (pour chaque appel successif) passée à + ​​side_effect +:

@pytest.fixture(params=['nodict', 'dict'])
def generate_initial_transform_parameters(request, mocker):
    [...]
    mocker.patch.object(outside_module, 'do_something')
    mocker.do_something.side_effect([1, 2])
    [...]

Le mocking est très puissant, si puissant que vous pouvez même set up mock servers to test third-party APIs et je vous encourage à nouveau faire une plongée plus profonde par vous-même dans la moquerie avec + mocker +.

Emballer

*Quand utiliser les frameworks de tests unitaires Python:*
  • Grands projets complexes

  • Projets OSS

    *Outils utiles:*
  • Pytest fixtures

  • deepdiff pour comparer des objets complexes

  • Moqueur

Avantages:

  • Automatise l’exécution des tests

  • Peut attraper de nombreux types de bugs

  • Installation et modification simples pour les équipes

Les inconvénients:

  • Fastidieux à écrire

  • Doit être mis à jour avec la plupart des changements de code

  • Ne répliquera pas la véritable application en cours d’exécution

Test d’intégration

Les tests d’intégration sont l’une des méthodes de test les plus simples ici, mais sans doute l’une des plus importantes. Cela implique d’exécuter votre application de bout en bout avec des données réelles dans un environnement de production.

Qu’il s’agisse de votre machine domestique, d’un serveur de test qui duplique un serveur de production ou simplement de changer une connexion à une base de données de test à partir d’une base de production, cela vous permet de savoir que vos modifications fonctionneront une fois déployées.

Comme dans toutes les autres méthodes, vous vérifiez que votre application génère les sorties attendues compte tenu de certaines entrées, sauf que cette fois, vous utilisez de vrais modules externes (contrairement aux tests unitaires, où ils sont simulés), peut-être en écrivant dans des bases de données réelles ou fichiers, et, dans les applications plus grandes, en s’assurant que votre code s’intègre bien avec le système global.

La façon dont vous procédez dépend fortement de votre application, par exemple, notre application de test peut être exécutée seule avec + python testapp.py +. Cependant, supposons que notre code soit un segment d’une grande application distribuée, comme un pipeline ETL - dans ce cas, vous devrez exécuter tout le système sur des serveurs de test avec votre code échangé, exécuter des données à travers lui et vous assurer qu’il a fait dans tout le système sous la bonne forme. En dehors du monde des applications en ligne de commande, des outils comme pyVows peuvent être utilisés pour tester l’intégration des applications Django.

Emballer

*Quand utiliser les tests d'intégration en Python:*
  • Toujours ;-)

  • Généralement après d’autres méthodes de test, si elles sont utilisées.

    *Outils utiles:*
  • tox gestion de l’environnement et de l’automatisation des tests

Avantages:

  • Découvrez le fonctionnement de votre application dans des conditions réelles

Les inconvénients:

  • Les applications plus grandes peuvent être difficiles à suivre avec précision le flux de données

  • Avoir des environnements de test très proches des environnements de production

Mettre tous ensemble

En conclusion, tous les tests CLI consistent à comparer vos sorties attendues à vos sorties réelles, compte tenu d’un ensemble d’entrées. Les méthodes dont j’ai parlé ci-dessus sont toutes des façons de faire exactement cela, et à bien des égards, elles sont complémentaires. Ces outils seront importants à comprendre lorsque vous continuerez à créer des applications de ligne de commande en Python, mais ce didacticiel n’est qu’un point de départ.

Python a un écosystème très riche, et cela s’étend aux outils et aux méthodologies de test, alors éloignez-vous de cela et étudiez plus - vous pouvez trouver un outil ou une technique que je n’ai pas mentionné ici que vous aimez absolument. Si c’est le cas, j’aimerais en entendre parler dans les commentaires!

Pour récapituler rapidement, voici les techniques que nous avons apprises aujourd’hui et comment elles sont utilisées:

  • Débogage d’impression - affichez les variables et autres marqueurs dans le code pour voir comment se déroule l’exécution

  • Débogueurs - contrôle de l’exécution du programme pour obtenir une vue d’ensemble de l’état de l’application et du flux du programme

  • Test unitaire - décomposer une application en unités testables individuellement et tester toutes les branches logiques au sein de cette unité

  • Test d’intégration - Test de vos modifications de code dans le contexte d’une application plus large

Maintenant, allez-y et testez! Lorsque vous travaillez avec ces techniques, assurez-vous de me dire dans les commentaires comment vous les avez utilisées et quelles sont vos préférées.

Pour obtenir une feuille de triche de test Python qui résume les techniques présentées dans ce didacticiel, cliquez sur le lien ci-dessous:

*Bonus gratuit:* lien: # [Cliquez ici pour obtenir notre aide-mémoire de test Python] qui résume les techniques présentées dans ce didacticiel.