Tests automatisés avec Pytest

Une traduction de l'article a été préparée spécialement pour les étudiants du cours Python QA Engineer .




Nous vivons à une époque où les logiciels évoluent très rapidement sur le marché. Pour cette raison, le processus de développement devient très stressant. Des taux élevés d'implémentation de logiciels et de livraison rapide semblent être une bonne partie du modèle d'entreprise, mais la question se pose ici de savoir comment fournir des logiciels de bonne qualité.

Pourquoi avons-nous besoin de tests automatisés


Les tests automatisés présentent de nombreux avantages, en voici trois principaux:
Réutilisation: il n'est pas nécessaire d'écrire de nouveaux scripts à chaque fois, même lors de la publication d'une nouvelle version du système d'exploitation, sauf en cas de besoin urgent.
Fiabilité: les gens ont tendance à faire des erreurs et les voitures les rendent moins probables. Et ils fonctionnent plus rapidement lors de l'exécution d'étapes / tests répétés qui doivent être effectués en continu.
Travail 24h / 24 et 7j / 7: vous pouvez commencer les tests à tout moment de la journée, même à distance. Si vous commencez le test la nuit, il s'exécutera même pendant votre sommeil.

Développement d'un outil de test Pytest complet en Python


Actuellement, il existe de nombreux cadres et outils pour les tests. Il existe différents types de frameworks, par exemple, pilotés par les données, pilotés par mots clés, hybrides, BDD, etc. Vous pouvez choisir celui qui correspond le mieux à vos besoins.

Je dois dire que Python et pytest occupent une énorme niche dans ce domaine. Python et ses outils associés sont largement utilisés, probablement parce qu'ils sont plus accessibles aux personnes ayant peu d'expérience en programmation par rapport aux autres langages.

Le cadre pytest facilite l'écriture de petits tests, mais il évolue également pour prendre en charge les tests fonctionnels sophistiqués des applications et des bibliothèques.

Quelques caractéristiques clés de pytest :

  • Détection automatique des modules et fonctions de test;
  • CLI efficace pour améliorer le contrôle sur ce que vous souhaitez exécuter ou ignorer;
  • Large écosystème tiers de plugins;
  • Fixtures - différents types, différentes applications;
  • Travailler avec le cadre traditionnel de tests unitaires.

Détection de test automatique et configurable


Par défaut, pytest s'attend à trouver des tests dans les modules Python dont les noms commencent par test_ ou se terminent par _test.py . De plus, par défaut, il s'attend à ce que les noms des fonctions de test commencent par le préfixe test_ . Cependant, ce protocole de détection de test peut être modifié en ajoutant votre propre configuration à l'un des pytest configuration pytest .

 # content of pytest.ini # Example 1: have pytest look for "check" instead of "test" # can also be defined in tox.ini or setup.cfg file, although the section # name in setup.cfg files should be "tool:pytest" [pytest] python_files = check_*.py python_classes = Check python_functions = *_check 

Regardons une fonction de test très simple:

 class CheckClass(object): def one_check(self): x = "this" assert 'h' in x def two_check(self): x = "hello" assert hasattr(x, 'check') 

Avez-vous remarqué quelque chose? Il n'y a pas d' assertEqual ou assertDictEqual , juste une assertDictEqual accessible et compréhensible. Il n'est pas nécessaire d'importer ces fonctions pour comparer simplement deux objets. Assert est ce que Python a déjà et il n'est pas nécessaire de réinventer la roue.

Code de modèle? Ne vous inquiétez pas, les luminaires se précipitent à la rescousse!


Regardez les fonctions de test qui testent les opérations de base dans le programme Wallet:

 // test_wallet.py from wallet import Wallet def test_default_initial_amount(): wallet = Wallet() assert wallet.balance == 0 wallet.close() def test_setting_initial_amount(): wallet = Wallet(initial_amount=100) assert wallet.balance == 100 wallet.close() def test_wallet_add_cash(): wallet = Wallet(initial_amount=10) wallet.add_cash(amount=90) assert wallet.balance == 100 wallet.close() def test_wallet_spend_cash(): wallet = Wallet(initial_amount=20) wallet.spend_cash(amount=10) assert wallet.balance == 10 wallet.close() 

Ahem, intéressant! Tu as remarqué? Il y a beaucoup de code passe-partout. Une autre chose à noter est que ce test fait autre chose que tester la partie fonctionnelle, par exemple, créer un portefeuille et le fermer avec wallet.close() .

Voyons maintenant comment vous pouvez vous débarrasser du code passe-partout en utilisant des pytest Pytest.

 import pytest from _pytest.fixtures import SubRequest from wallet import Wallet #==================== fixtures @pytest.fixture def wallet(request: SubRequest): param = getattr(request, 'param', None) if param: prepared_wallet = Wallet(initial_amount=param[0]) else: prepared_wallet = Wallet() yield prepared_wallet prepared_wallet.close() #==================== tests def test_default_initial_amount(wallet): assert wallet.balance == 0 @pytest.mark.parametrize('wallet', [(100,)], indirect=True) def test_setting_initial_amount(wallet): assert wallet.balance == 100 @pytest.mark.parametrize('wallet', [(10,)], indirect=True) def test_wallet_add_cash(wallet): wallet.add_cash(amount=90) assert wallet.balance == 100 @pytest.mark.parametrize('wallet', [(20,)], indirect=True) def test_wallet_spend_cash(wallet): wallet.spend_cash(amount=10) assert wallet.balance == 10 

C'est bien, non? Les fonctions de test sont désormais compactes et font exactement ce qu'elles devraient faire. Le portefeuille est configuré, installé et fermé à l'aide du wallet . Les appareils non seulement aident à écrire du code réutilisable, mais ajoutent également le concept de partage de données. Si vous regardez de plus près, la quantité dans le wallet fait partie des données de test fournies en externe par la logique de test et n'est pas fixée de manière rigide à l'intérieur de la fonction.

 @pytest.mark.parametrize('wallet', [(10,)], indirect=True) 

Dans un environnement plus contrôlé, vous pouvez avoir un fichier avec des données de test, par exemple test-data.ini dans votre référentiel ou shell, qui peut le lire, tandis que votre fonction de test peut appeler différents shells pour lire les données de test.

Il est cependant recommandé de placer tous vos appareils dans un fichier conftest.py spécial. Il s'agit d'un fichier spécial dans pytest qui permet au test de détecter les appareils globaux.

Mais j'ai des cas de test que je veux exécuter sur différents jeux de données!


Ne vous inquiétez pas, pytest a une fonctionnalité intéressante pour paramétrer votre appareil. Regardons un exemple.

Supposons que votre produit dispose d'une CLI gérée localement. De plus, votre produit possède de nombreux paramètres par défaut définis au démarrage et vous souhaitez vérifier toutes les valeurs de ces paramètres.

Vous pourriez penser à écrire un cas de test séparé pour chacun de ces paramètres, mais avec pytest tout est beaucoup plus simple!

 @pytest.mark.parametrize(“setting_name, setting_value”, [('qdb_mem_usage', 'low'), ('report_crashes', 'yes'), ('stop_download_on_hang', 'no'), ('stop_download_on_disconnect', 'no'), ('reduce_connections_on_congestion', 'no'), ('global.max_web_users', '1024'), ('global.max_downloads', '5'), ('use_kernel_congestion_detection', 'no'), ('log_type', 'normal'), ('no_signature_check', 'no'), ('disable_xmlrpc', 'no'), ('disable_ntp', 'yes'), ('ssl_mode', 'tls_1_2'),])def test_settings_defaults(self, setting_name, setting_value): assert product_shell.run_command(setting_name) == \ self.”The current value for \'{0}\' is \'{1}\'.”.format(setting_name, setting_value), \ 'The {} default should be {}'.format(preference_name, preference_value) 

Cool, non? Vous venez d'écrire 13 cas de test (chacun définit une valeur de setting_value différente), et à l'avenir, si vous ajoutez un nouveau paramètre à votre produit, tout ce que vous devez faire est d'ajouter un autre tuple.

Comment pytest s'intègre-t-il aux tests d'interface utilisateur avec Selenium et aux tests API?


Eh bien, votre produit peut avoir plusieurs interfaces. CLI - comme nous l'avons dit ci-dessus. Similaire à l'interface graphique et à l'API. Avant de déployer votre produit logiciel, il est important de les tester tous. Dans les logiciels d'entreprise, où plusieurs composants sont interconnectés et dépendent les uns des autres, une modification d'une partie peut affecter toutes les autres.

N'oubliez pas que pytest n'est qu'un cadre pour des tests faciles, pas un type spécifique de test. Autrement dit, vous pouvez créer des tests pour l'interface graphique à l'aide de Selenium ou, par exemple, des tests pour l'API avec la bibliothèque de requests de Python et les exécuter avec pytest .

Par exemple, à un niveau élevé, cela peut être une vérification de la structure du référentiel.



Comme vous le voyez dans l'image ci-dessus, cela donne une bonne occasion de séparer les composants:

apiobjects : un bon endroit pour créer des wrappers pour appeler des points de terminaison d'API. Vous pouvez avoir un BaseAPIObject et une classe dérivée qui répondent à vos besoins.

helpers : Vous pouvez ajouter vos méthodes d'assistance ici.

lib : fichiers de bibliothèque pouvant être utilisés par divers composants, par exemple, vos conftest dans conftest , pageobjects , etc.

pageobjects : le modèle d' architecture PageObjects peut être utilisé pour créer des classes pour diverses pages GUI. Nous utilisons Webium , qui est une bibliothèque d'implémentations de modèles d'objet de page pour Python.

suites : vous pouvez écrire vos propres ensembles de vérifications de code pour le code, ils vous aideront à gagner plus de confiance dans la qualité de votre code.

tests : vous pouvez cataloguer les tests en fonction de vos préférences. Cela facilitera la gestion et la révision de vos tests.

Je l'ai apporté juste pour référence, la structure du référentiel et les dépendances peuvent être organisées en fonction de vos besoins personnels.

J'ai de nombreux cas de test et je veux qu'ils fonctionnent en parallèle


Vous pouvez avoir de nombreux cas de test dans votre ensemble, et il arrive que vous deviez les exécuter en parallèle et réduire le temps d'exécution global des tests.

Pytest propose un plug-in étonnant pour exécuter des tests en parallèle appelé pytest-xdist , qui ajoute plusieurs modes d'exécution uniques au pytest de base. Installez ce plugin à l'aide de pip .

 pip install pytest-xdist 

Voyons comment cela fonctionne avec un exemple.

J'ai un référentiel de tests automatisé CloudApp pour mes tests Selenium GUI. De plus, il est en constante augmentation et mis à jour avec de nouveaux tests et il a maintenant des centaines de tests. Ce que je veux faire, c'est les exécuter en parallèle et réduire le temps d'exécution global des tests.

Dans le terminal, tapez simplement pytest dans le dossier racine du projet / dossier de test. Cela vous permettra d'exécuter tous les tests.

 pytest -s -v -n=2 



pytest-xdist exécutera tous les tests en parallèle!

De cette façon, vous pouvez également exécuter plusieurs navigateurs en parallèle.

Rapports


Pytest est livré avec un support intégré pour créer des fichiers de résultats de test qui peuvent être ouverts à l'aide de Jenkins, Bamboo ou d'autres serveurs d'intégration continue. Utilisez ce qui suit:

 pytest test/file/path — junitxml=path 

Cela aidera à générer un excellent fichier XML qui peut être ouvert avec de nombreux analyseurs.

Conclusion


La popularité de Pytest augmente chaque année. En outre, il dispose d'un puissant support communautaire, qui vous permet d'accéder à de nombreuses extensions, telles que pytest-django , qui vous aideront à écrire des tests pour les applications Web dans Django. N'oubliez pas que pytest prend en charge les cas de test les plus intacts, donc si vous utilisez unittest , pytest doit être considéré plus en détail.

Les sources



C’est tout. Rendez-vous sur le parcours !

Source: https://habr.com/ru/post/fr480186/


All Articles