Test Python avec pytest. Configuration, CHAPITRE 6

Retour Suivant


Dans ce chapitre, nous examinerons les fichiers de configuration qui affectent pytest, discuterons de la façon dont pytest modifie son comportement en fonction d'eux et apporterons quelques modifications aux tùches de configuration du projet Tùches.



Les exemples de ce livre sont écrits en utilisant Python 3.6 et pytest 3.2. pytest 3.2 prend en charge Python 2.6, 2.7 et Python 3.3+.


Le code source du projet Tùches, ainsi que pour tous les tests présentés dans ce livre, est disponible sur le lien sur la page Web du livre à pragprog.com . Vous n'avez pas besoin de télécharger le code source pour comprendre le code de test; le code de test est présenté sous une forme pratique dans les exemples. Mais pour suivre les tùches du projet ou adapter des exemples de test pour tester votre propre projet (vos mains ne sont pas liées!), Vous devez vous rendre sur la page Web du livre et télécharger le travail. Là, sur la page Web du livre, il y a un lien pour les messages d' erreur et un forum de discussion .

Sous le spoiler se trouve une liste d'articles de cette série.



La configuration


Jusqu'à présent, dans ce livre, j'ai parlé de divers fichiers non-test qui affectent pytest principalement en passant, à l'exception de conftest.py, que j'ai traité en détail dans le chapitre 5, Plugins, à la page 95. Dans ce chapitre, nous examinerons les fichiers de configuration, qui affectent pytest, discutez de la maniÚre dont pytest modifie son comportement en fonction de ceux-ci et apportez quelques modifications aux fichiers de configuration du projet Tùches.


Comprendre les fichiers de configuration pytest


Avant de vous dire comment vous pouvez changer le comportement par défaut dans pytest, passons en revue tous les fichiers non test dans pytest et, en particulier, qui devrait s'en occuper.


Vous devez savoir ce qui suit:


  • pytest.ini : il s'agit du fichier de configuration Pytest principal qui vous permet de modifier le comportement par dĂ©faut. Étant donnĂ© que vous pouvez effectuer plusieurs modifications de configuration, la majeure partie de ce chapitre est consacrĂ©e aux paramĂštres que vous pouvez effectuer dans pytest.ini .
  • conftest.py : il s'agit d'un plugin local qui permet aux fonctions de hook et aux appareils d'ĂȘtre connectĂ©s au rĂ©pertoire dans lequel le fichier conftest.py et Ă  tous ses sous-rĂ©pertoires. Le fichier conftest.py dĂ©crit dans le chapitre 5 «Plugins» Ă  la page 95.
  • __init__.py : Lorsqu'il est placĂ© dans chaque sous-rĂ©pertoire de test, ce fichier vous permet d'avoir des noms de fichier de test identiques dans plusieurs rĂ©pertoires de test. Nous allons voir un exemple de ce qui pourrait mal se passer sans les fichiers __init__.py dans les rĂ©pertoires de test dans l'article «Éviter les collisions de noms de fichiers» Ă  la page 120.

Si vous utilisez tox, vous serez intéressé par:


  • tox.ini : Ce fichier est similaire Ă  pytest.ini , mais pour tox . Cependant, vous pouvez placer votre configuration pytest au lieu d'avoir Ă  la fois le fichier tox.ini et le fichier pytest.ini , vous Ă©conomisant un fichier de configuration. Tox est abordĂ© au chapitre 7, «Utilisation de pytest avec d'autres outils», page 125.

Si vous souhaitez distribuer un package Python (par exemple Tùches), ce fichier sera intéressant:


  • setup.cfg : il s'agit Ă©galement d'un fichier INI qui affecte le comportement de setup.py . Vous pouvez ajouter quelques lignes Ă  setup.py pour exĂ©cuter le python setup.py test et exĂ©cuter tous vos tests pytest. Si vous distribuez le package, vous disposez peut-ĂȘtre dĂ©jĂ  du fichier setup.cfg et vous pouvez utiliser ce fichier pour stocker la configuration Pytest. Vous verrez comment cela se fait dans l'annexe 4, «Empaquetage et distribution de projets Python», page 175.

Quel que soit le fichier dans lequel vous mettez la configuration pytest, le format sera fondamentalement le mĂȘme.


Pour pytest.ini :


ch6 / format / pytest.ini

 [pytest] addopts = -rsxX -l --tb=short --strict xfail_strict = true ... more options ... 

Pour tox.ini :


ch6 / format / tox.ini

 ... tox specific stuff ... [pytest] addopts = -rsxX -l --tb=short --strict xfail_strict = true ... more options ... 

Pour setup.cfg :


ch6 / format / setup.cfg

 ... packaging specific stuff ... [tool:pytest] addopts = -rsxX -l --tb=short --strict xfail_strict = true ... more options ... 

La seule diffĂ©rence est que l'en-tĂȘte de section pour setup.cfg est [tool:pytest] au lieu de [pytest] .


Liste des options valides de fichier ini avec pytest –help


Vous pouvez obtenir une liste de tous les paramĂštres valides pour pytest.ini partir de pytest --help :


 $ pytest --help ... [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: markers (linelist) markers for test functions empty_parameter_set_mark (string) default marker for empty parametersets norecursedirs (args) directory patterns to avoid for recursion testpaths (args) directories to search for tests when no files or directories are given in the command line. console_output_style (string) console output: classic or with additional progress information (classic|progress). usefixtures (args) list of default fixtures to be used with this project python_files (args) glob-style file patterns for Python test module discovery python_classes (args) prefixes or glob names for Python test class discovery python_functions (args) prefixes or glob names for Python test function and method discovery xfail_strict (bool) default for the strict parameter of xfail markers when not given explicitly (default: False) junit_suite_name (string) Test suite name for JUnit report junit_logging (string) Write captured log messages to JUnit report: one of no|system-out|system-err doctest_optionflags (args) option flags for doctests doctest_encoding (string) encoding used for doctest files cache_dir (string) cache directory path. filterwarnings (linelist) Each line specifies a pattern for warnings.filterwarnings. Processed after -W and --pythonwarnings. log_print (bool) default value for --no-print-logs log_level (string) default value for --log-level log_format (string) default value for --log-format log_date_format (string) default value for --log-date-format log_cli (bool) enable log display during test run (also known as "live logging"). log_cli_level (string) default value for --log-cli-level log_cli_format (string) default value for --log-cli-format log_cli_date_format (string) default value for --log-cli-date-format log_file (string) default value for --log-file log_file_level (string) default value for --log-file-level log_file_format (string) default value for --log-file-format log_file_date_format (string) default value for --log-file-date-format addopts (args) extra command line options minversion (string) minimally required pytest version xvfb_width (string) Width of the Xvfb display xvfb_height (string) Height of the Xvfb display xvfb_colordepth (string) Color depth of the Xvfb display xvfb_args (args) Additional arguments for Xvfb xvfb_xauth (bool) Generate an Xauthority token for Xvfb. Needs xauth. ... 

Vous verrez tous ces paramÚtres dans ce chapitre, à l'exception de doctest_optionflags , qui est abordé dans le chapitre 7, «Utilisation de pytest avec d'autres outils», page 125.


Les plugins peuvent ajouter des options de fichier ini


La liste de paramÚtres précédente n'est pas une constante. Pour les plugins (et les fichiers conftest.py), il est possible d'ajouter des options de fichier ini. Les options ajoutées seront également ajoutées à la sortie de la commande pytest --help.
Examinons maintenant quelques modifications de configuration que nous pouvons effectuer en utilisant les paramÚtres intégrés du fichier .ini disponibles dans core pytest.


Modifier les options de ligne de commande par défaut


Vous avez dĂ©jĂ  utilisĂ© certaines options de ligne de commande pour pytest , telles que -v/--verbose pour la sortie verbeuse -l/--showlocals pour afficher les variables locales avec une trace de pile pour les tests ayant Ă©chouĂ©. Vous constaterez peut-ĂȘtre que vous utilisez toujours certaines de ces options—or que vous prĂ©fĂ©rez les utiliser them—for a project . Si vous installez addopts dans pytest.ini pour les paramĂštres dont vous avez besoin, vous n'avez plus Ă  les saisir. Voici un ensemble que j'aime:


 [pytest] addopts = -rsxX -l --tb=short --strict 

Le -rsxX permet à pytest de signaler les raisons de tous les tests skipped , xfailed ou xpassed . Le commutateur -l permet à pytest d'afficher une trace de pile pour les variables locales en cas de chaque échec. --tb=short supprimera la majeure partie de la trace de la pile. Cependant, il laissera le fichier et le numéro de ligne. L' --strict désactive l'utilisation des jetons s'ils ne sont pas enregistrés dans le fichier de configuration. Vous verrez comment procéder dans la section suivante.


Enregistrement des marqueurs pour éviter les fautes de frappe


Les marqueurs personnalisĂ©s, comme dĂ©crit dans «Étiquetage des fonctions de test» Ă  la page 31, sont parfaits pour vous permettre de marquer un sous-ensemble de tests Ă  exĂ©cuter avec un marqueur spĂ©cifique. Cependant, il est trop facile de faire une erreur dans le marqueur et finalement certains tests sont marquĂ©s @pytest.mark.smoke et certains sont marquĂ©s @pytest.mark.somke . Par dĂ©faut, ce n'est pas une erreur. pytest pense juste que vous avez créé deux marqueurs. Cependant, cela peut ĂȘtre rĂ©solu en enregistrant des jetons dans pytest.ini, par exemple comme ceci:


 [pytest] ... markers = smoke: Run the smoke test test functions get: Run the test functions that test tasks.get() ... 

En enregistrant ces marqueurs, vous pouvez désormais les voir également avec les pytest --markers avec leurs descriptions:


 $ cd /path/to/code/ch6/b/tasks_proj/tests $ pytest --markers @pytest.mark.smoke: Run the smoke test test functions @pytest.mark.get: Run the test functions that test tasks.get() @pytest.mark.skip(reason=None): skip the ... ... 

Si les marqueurs ne sont pas enregistrés, ils n'apparaßtront pas dans la liste --markers . Lorsqu'ils sont enregistrés, ils sont affichés dans la liste et si vous utilisez --strict , tous les jetons avec des erreurs ou non enregistrés sont affichés comme des erreurs. La seule différence entre ch6/a/tasks_proj et ch6/b/tasks_proj est le contenu du fichier pytest.ini. Dans ch6/a vide. Essayons d'exécuter les tests sans enregistrer de marqueurs:


 $ cd /path/to/code/ch6/a/tasks_proj/tests $ pytest --strict --tb=line ============================= test session starts ============================= collected 45 items / 2 errors =================================== ERRORS ==================================== ______________________ ERROR collecting func/test_add.py ______________________ 'smoke' not a registered marker ________________ ERROR collecting func/test_api_exceptions.py _________________ 'smoke' not a registered marker !!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!! =========================== 2 error in 1.10 seconds =========================== 

Si vous utilisez des marqueurs dans pytest.ini pour enregistrer vos marqueurs, vous pouvez Ă©galement ajouter --strict Ă  vos addopts pendant que vous y ĂȘtes. Tu me remercieras plus tard. Allons de l'avant et ajoutons un fichier pytest.ini au projet de tĂąches:


Si vous utilisez des marqueurs dans pytest.ini pour enregistrer vos marqueurs, vous pouvez également ajouter --strict à vos addopts . Vous me remercierez plus tard. Continuons et ajoutons le fichier pytest.ini au projet de tùche:


Si vous utilisez des jetons dans pytest.ini pour enregistrer des jetons, vous pouvez également ajouter --strict à ceux existants avec addopts . Cool?! pytest.ini remerciements et ajoutez le fichier pytest.ini au projet de tasks :


ch6 / b / tasks_proj / tests / pytest.ini

 [pytest] addopts = -rsxX -l --tb=short --strict markers = smoke: Run the smoke test test functions get: Run the test functions that test tasks.get() 

Voici la combinaison de drapeaux préférée par défaut:


  • -rsxX pour -rsxX quels tests sont ignorĂ©s, Ă©chouĂ©s ou xpassĂ©s,
  • --tb = short pour une trace plus courte des pannes,
  • --strict pour autoriser uniquement les jetons dĂ©clarĂ©s.
    Et une liste de marqueurs pour le projet.

Cela devrait nous permettre d'effectuer des tests, y compris des tests de fumée:


 $ cd /path/to/code/ch6/b/tasks_proj/tests $ pytest --strict -m smoke ===================== test session starts ====================== collected 57 items func/test_add.py . func/test_api_exceptions.py .. ===================== 54 tests deselected ====================== =========== 3 passed, 54 deselected in 0.06 seconds ============ 

Exigence minimale de Pytest


Le paramÚtre minversion permet de spécifier la version minimale de pytest attendue pour les tests. Par exemple, j'avais l'intention d'utiliser approx() lors du test de nombres à virgule flottante pour déterminer l'égalité «assez proche» dans les tests. Mais cette fonction n'a été introduite dans pytest qu'à partir de la version 3.0. Pour éviter toute confusion, j'ajoute ce qui suit aux projets qui utilisent approx() :


 [pytest] minversion = 3.0 

Ainsi, si quelqu'un essaie d'exécuter des tests à l'aide d'une ancienne version de pytest, un message d'erreur apparaßt.


EmpĂȘchez Pytest de chercher au mauvais endroit


Saviez-vous que l'une des définitions de «recurse» est de jurer deux fois dans votre propre code? Et bien non. En fait, cela signifie tenir compte des sous-répertoires. pytest permettra la détection des tests en examinant récursivement un tas de répertoires. Mais il y a certains répertoires que vous souhaitez exclure de l'affichage de pytest.


La valeur par défaut de norecurse est '. * Build dist CVS _darcs {arch} and *.egg. Having '.*' '. * Build dist CVS _darcs {arch} and *.egg. Having '.*' '. * Build dist CVS _darcs {arch} and *.egg. Having '.*' Est une bonne raison de nommer votre environnement virtuel '.venv', car tous les répertoires commençant par un point ne seront pas visibles.


Dans le cas du projet Tùches, src n'aura pas de mal à le spécifier, car la recherche dans les fichiers de test à l'aide de pytest sera une perte de temps.


 [pytest] norecursedirs = .* venv src *.egg dist build 

Lorsque vous remplacez un paramÚtre qui a déjà une valeur utile, comme ce paramÚtre, il est utile de connaßtre les valeurs par défaut et de renvoyer celles dont vous avez besoin, comme je l'ai fait dans le code précédent avec la construction *.egg dist build .
norecursedirs est une sorte de consĂ©quence pour les chemins de test, nous allons donc y jeter un Ɠil plus tard.


tester la spécification de l'arborescence


Alors que norecursedirs indique Ă  pytest oĂč chercher, testpaths indique Ă  pytest oĂč chercher. testspaths est une liste de rĂ©pertoires relatifs au rĂ©pertoire racine pour rechercher des tests. Il n'est utilisĂ© que si le rĂ©pertoire, le fichier ou le nodeid pas spĂ©cifiĂ© comme argument.


Supposons que pour un projet Tasks , nous plaçons pytest.ini dans le répertoire tasks_proj au lieu de tests:


 \code\tasks_proj>tree/f . │ pytest.ini │ ├───src │ └───tasks │ api.py │ ... │ └───tests │ conftest.py │ pytest.ini │ ├───func │ test_add.py │ ... │ ├───unit │ test_task.py │ __init__.py │ ... 

Ensuite, il pourrait ĂȘtre judicieux de placer les tests dans des testpaths de testpaths :


 [pytest] testpaths = tests 

Maintenant, si vous exécutez pytest à partir du répertoire tasks_proj, pytest ne recherchera que dans tasks_proj/tests . Le problÚme ici est que lors du développement et du débogage des tests, je parcours souvent le répertoire de test, donc je peux facilement tester un sous-répertoire ou un fichier sans spécifier le chemin complet. Par conséquent, cette option m'aide un peu dans les tests interactifs.


Cependant, il est idĂ©al pour les tests exĂ©cutĂ©s Ă  partir d'un serveur d'intĂ©gration continue ou tox. Dans ces cas, vous savez que le rĂ©pertoire racine sera fixe et vous pouvez rĂ©pertorier les rĂ©pertoires relatifs Ă  ce rĂ©pertoire racine fixe. Ce sont Ă©galement les cas oĂč vous voulez vraiment rĂ©duire le temps de test, donc se dĂ©barrasser de la recherche de tests est super.


À premiĂšre vue, il peut sembler idiot d'utiliser Ă  la fois des chemins de test et des norecursedirs . Cependant, comme vous l'avez dĂ©jĂ  vu, les chemins de test aident peu dans les tests interactifs Ă  partir de diffĂ©rentes parties du systĂšme de fichiers. Dans ces cas, les norecursedirs peuvent aider. De plus, si vous avez des rĂ©pertoires de test qui ne contiennent pas de tests, vous pouvez utiliser norecursedirs pour les Ă©viter. Mais en fait, quel est l'intĂ©rĂȘt de mettre des rĂ©pertoires supplĂ©mentaires dans des tests qui n'ont pas de tests?


Modification des rÚgles de détection des tests


pytest trouve les tests à exécuter en fonction de rÚgles de découverte de test spécifiques. RÚgles de détection de test standard:


‱ Commencez avec un ou plusieurs rĂ©pertoires. Vous pouvez spĂ©cifier les noms de fichiers ou de rĂ©pertoires sur la ligne de commande. Si vous n'avez rien spĂ©cifiĂ©, le rĂ©pertoire courant est utilisĂ©.
‱ Rechercher dans le catalogue et tous ses sous-rĂ©pertoires des modules de test.
‱ Un module de test est un fichier avec un nom similaire à test_*.py ou *_test.py .
‱ Recherchez dans les modules de test les fonctions qui commencent par test .
‱ Recherchez les classes qui commencent par Test. Recherchez les mĂ©thodes dans les classes qui commencent par `test , init` .


Ce sont des rÚgles de détection standard; Cependant, vous pouvez les modifier.


python_classes


La rÚgle habituelle pour trouver des tests pour pytest et des classes est de considérer une classe comme une classe de test potentielle si elle commence par Test* . La classe ne peut pas non plus avoir la __init__() . Mais que se passe-t-il si nous voulons nommer nos classes de test en tant que <something>Test ou <something>Suite ? C'est là python_classes :


 [pytest] python_classes = *Test Test* *Suite 

Cela nous permet de nommer les classes comme ceci:


 class DeleteSuite(): def test_delete_1(): ... def test_delete_2(): ... .... 

python_files


Comme pytest_classes , python_files modifie la rÚgle de détection de test par défaut, qui consiste à rechercher des fichiers commençant par test_* ou ayant *_test à la fin.
Supposons que vous ayez un cadre de test personnalisé dans lequel vous avez nommé tous vos fichiers de test check_<something>.py . Semble raisonnable. Au lieu de renommer tous vos fichiers, ajoutez simplement une ligne à pytest.ini comme suit:


 [pytest] python_files = test_* *_test check_* 

TrÚs simple. Vous pouvez maintenant transférer progressivement la convention de dénomination si vous le souhaitez, ou simplement la laisser sous check_* .


fonctions_python


python_functions agit comme les deux paramÚtres précédents, mais pour les fonctions de test et les noms de méthode. La valeur par défaut est test_* . Et pour ajouter check_* deviné - faites ceci:


 [pytest] python_functions = test_* check_* 

Les pytest dénomination pytest ne semblent pas si restrictives, n'est-ce pas? Donc, si vous n'aimez pas la convention de dénomination par défaut, changez-la simplement. Néanmoins, je vous exhorte à avoir une raison plus impérieuse pour de telles décisions. La migration de centaines de fichiers de test est certainement une bonne raison.


Interdiction XPASS


La dĂ©finition de xfail_strict = true signifie que les tests marquĂ©s avec @pytest.mark.xfail ne sont pas reconnus comme provoquant l'erreur. Je pense que ce paramĂštre devrait toujours ĂȘtre. Pour plus d'informations sur le jeton xfail voir «Marquage des tests en attente d'Ă©chec», page 37.


EmpĂȘcher les conflits de noms de fichiers


L'utilitĂ© d'avoir le fichier __init__.py dans chaque sous-rĂ©pertoire de test du projet m'a longtemps dĂ©routĂ©. Cependant, la diffĂ©rence entre les avoir ou ne pas les avoir est simple. Si vous avez des fichiers __init__.py dans tous vos sous-rĂ©pertoires de test, vous pouvez avoir le mĂȘme nom de fichier de test dans plusieurs rĂ©pertoires. Et sinon, cela ne fonctionnera pas.


Voici un exemple. Les répertoires a et b ont tous deux le fichier test_foo.py . Peu importe ce que ces fichiers contiennent, mais pour cet exemple, ils ressemblent à ceci:


ch6 / dups / a / test_foo.py
 def test_a(): pass 


ch6 / dups / b / test_foo.py
 def test_b(): pass 

Avec cette structure de répertoire:


 dups ├── a │ └── test_foo.py └── b └── test_foo.py 

Ces fichiers n'ont mĂȘme pas le mĂȘme contenu, mais les tests sont corrompus. Vous pourrez les exĂ©cuter sĂ©parĂ©ment, mais il n'y a aucun moyen d'exĂ©cuter pytest partir du rĂ©pertoire dups :


 $ cd /path/to/code/ch6/dups $ pytest a ============================= test session starts ============================= collected 1 item a\test_foo.py . ========================== 1 passed in 0.05 seconds =========================== $ pytest b ============================= test session starts ============================= collected 1 item b\test_foo.py . ========================== 1 passed in 0.05 seconds =========================== $ pytest ============================= test session starts ============================= collected 1 item / 1 errors =================================== ERRORS ==================================== _______________________ ERROR collecting b/test_foo.py ________________________ import file mismatch: imported module 'test_foo' has this __file__ attribute: /path/to/code/ch6/dups/a/test_foo.py which is not the same as the test file we want to collect: /path/to/code/ch6/dups/b/test_foo.py HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules !!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!! =========================== 1 error in 0.34 seconds =========================== 

Rien n'est clair!
Ce message d'erreur n'indique pas ce qui s'est mal passé.


Pour corriger ce test, ajoutez simplement le fichier __init__.py vide aux sous-rĂ©pertoires. Voici un exemple du rĂ©pertoire dups_fixed avec les mĂȘmes noms de fichiers en double, mais avec des fichiers __init__.py ajoutĂ©s:


 dups_fixed/ ├── a │ ├── __init__.py │ └── test_foo.py └── b ├── __init__.py └── test_foo.py 

Essayons maintenant à nouveau à partir du niveau supérieur dans dups_fixed :


 $ cd /path/to/code/ch6/ch6/dups_fixed/ $ pytest ============================= test session starts ============================= collected 2 items a\test_foo.py . b\test_foo.py . ========================== 2 passed in 0.15 seconds =========================== 

Ce sera donc mieux.


Bien sûr, vous pouvez vous convaincre que vous n'aurez jamais de noms de fichiers en double, donc cela n'a pas d'importance. Tout, comme, est normal. Mais les projets grandissent et les catalogues de tests se développent, et vous voulez vraiment attendre que cela vous arrive avant de vous en occuper? Je dis, il suffit de mettre ces fichiers là-bas. Faites-en une habitude et ne vous en souciez plus.


Exercices


Au Chapitre 5, Plugins, Ă  la page 95, vous avez créé un plugin appelĂ© pytest-nice qui incluait une option de ligne de commande --nice. Étendons cela pour inclure une option pytest.ini appelĂ©e nice.


Au chapitre 5, «Plugins» à la page 95, vous avez créé un plugin appelé pytest-nice qui inclut l' --nice ligne de commande --nice . Développons ceci pour inclure l'option pytest.ini appelée nice .


  1. Ajoutez la ligne suivante Ă  la fonction de crochet pytest_addoption pytest_nice.py : parser.addini('nice', type='bool', help='Turn failures into opportunities.')
  2. Les endroits du plugin qui utilisent getoption() devront également appeler getini('nice') . Apportez ces modifications.
  3. Vérifiez cela manuellement en ajoutant nice au fichier pytest.ini .
  4. N'oubliez pas les tests de plugin. Ajoutez un test pour vérifier que le nice paramÚtre de pytest.ini fonctionne correctement.
  5. Ajoutez des tests au répertoire du plugin. Vous devez trouver des fonctionnalités supplémentaires de Pytester .

Et ensuite


Bien que pytest soit extrĂȘmement puissant en soi - en particulier avec les plugins - il s'intĂšgre Ă©galement bien avec d'autres outils de dĂ©veloppement et de test de logiciels. Dans le chapitre suivant, nous examinerons l'utilisation de pytest conjointement avec d'autres outils de test puissants.


Retour Suivant

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


All Articles