trouver un expert Python
Dans de nombreuses circonstances, il est vraiment génial d’afficher le résultat d’un test en cours, sans attendre la fin.
Dans cet article, je vais décrire ma méthode pour contourner le mécanisme de capture de sortie de pytest, afin que je puisse voir mes instructions de débogage / journalisation en temps réel.
Rapide et rapide? Pas toujours
Les tests unitaires doivent être rapides afin que toute la suite de tests puisse être exécutée rapidement.
Mais quelle est la définition d’un test unitaire?
Et qu'est-ce que "rapide" et "rapidement" exactement?
Et que se passe-t-il si je n’effectue pas vraiment des tests unitaires traditionnels, mais plutôt des «unités de fonctionnalité» ou des «unités de fonctionnalité»?
Et si je suis ce que je teste réellement, c’est tout le système, avec des retards de communication, des temps de réglage du matériel, des temps d’acquisition des mesures et qui sait quelles sont toutes les autres latences du système.
Il existe de nombreux tests qui sont intéressants à exécuter à partir d’un framework de tests unitaires qui ne correspondent pas à la définition traditionnelle d’un test unitaire rapide.
J'ai des tests qui durent plusieurs minutes.
Et des suites qui durent une heure et demie ou plus.
Et pendant ce temps, je veux savoir ce qui se passe.
Les instructions de statut de débogage sont idéales pour me faire savoir ce qui se passe pendant l'exécution.
Ils fonctionnent très bien avec du code python normal non testé.
Mais lorsque je mets des instructions print ou logging en code de test, elles sont capturées et masquées.
C’est peut-être une bonne chose la plupart du temps.
Je pense que c'était une bonne décision d'en faire le comportement par défaut.
Cependant, je veux parfois le contourner.
Je vais montrer ci-dessous quelques exemples de ce dont je parle et montrer comment je le résous.
Exemple de tests lents, utilisant des instructions d'impression
Commençons par quelques tests simples qui durent plus de 3 secondes chacun.
slowTest_print.py
temps d'importation
def test_1 ():
affiche 'test_1'
heure (1)
print 'après 1 seconde'
heure (1)
print 'après 2 secondes'
heure (1)
print 'après 3 secondes'
affirmer 1, 'devrait passer'
def test_2 ():
print 'in test_2'
heure (1)
print 'après 1 seconde'
heure (1)
print 'après 2 secondes'
heure (1)
print 'après 3 secondes'
affirmer 0, 'échec pour démonstration'
1 2 3 4 5 6 7 8 9 dix 11 12 13 14 15 16 17 18 19 20 21 22 |
importation temps def test_1(): impression 'test_1' temps.dormir(1) impression 'après 1 seconde' temps.dormir(1) impression 'après 2 secondes' temps.dormir(1) impression 'après 3 secondes' affirmer 1, 'devrait passer' def test_2(): impression 'dans test_2' temps.dormir(1) impression 'après 1 seconde' temps.dormir(1) impression 'après 2 secondes' temps.dormir(1) impression 'après 3 secondes' affirmer 0, 'échec pour démonstration' |
Si je l’exécute normalement, pytest capturera l’ensemble de la sortie, l’enregistrera jusqu’à ce que tous les tests soient terminés, et affichera la sortie des tests ayant échoué.
> py.test slowTest_print.py
============================== La session de test commence ================= ============
plate-forme win32 – Python 2.7.3 – pytest-2.3.4
collectionné 2 articles
slowTest_print.py .F
================================== FAILURES ============== ======================
____________________________________ test_2 ____________________________________
def test_2 ():
print 'in test_2'
heure (1)
print 'après 1 seconde'
heure (1)
print 'après 2 secondes'
heure (1)
print 'après 3 secondes'
> assert 0, 'échec de la démonstration'
E AssertionError: échec à des fins de démonstration
slowTest_print.py:26: AssertionError
——————————- Capturé stdout —————– —————
dans test_2
après 1 seconde
après 2 secondes
après 3 secondes
====================== 1 échec, 1 échec en 6,02 secondes ==================== ==
1 2 3 4 5 6 7 8 9 dix 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
> py.tester slowTest_print.py ============================= tester session départs ============================== Plate-forme win32 – Python 2.7.3 – pytest–2.3.4 collecté 2 articles slowTest_print.py .F =================================== LES ÉCHECS =================================== ____________________________________ test_2 ____________________________________ def test_2(): impression 'dans test_2' temps.dormir(1) impression 'après 1 seconde' temps.dormir(1) impression 'après 2 secondes' temps.dormir(1) impression 'après 3 secondes' > affirmer 0, 'échec pour démonstration' E AssertionError: échouer pour démo fins slowTest_print.py:26: AssertionError –––––––––––––––– Capturé stdout –––––––––––––––– dans test_2 après 1 seconde après 2 seconde après 3 seconde ====================== 1 échoué, 1 passé dans 6.02 secondes ====================== |
Je peux utiliser le drapeau ‘-s’ ou ‘–capture = no’, puis tout le résultat est signalé.
Cependant, pytest attend toujours que tous les tests soient effectués pour afficher les résultats.
Pour autant que je sache, il n’ya aucun moyen de le contourner à l’aide de déclarations «imprimées».
Mise à jour: Ceci est apparemment un problème d'environnement. OOPS. Les «-s» et «–capture = no» fonctionnent bien dans de nombreux environnements. Le seul environnement où je l’ai vu échouer est celui qui consiste à exécuter des tests de bash sur cygwin. J'aurais dû le tester dans plusieurs environnements avant de poster. OOPS. Merci à Joe de me l'avoir signalé.
> py.test –capture = no slowTest_print.py
============================== La session de test commence ================= ============
plate-forme win32 – Python 2.7.3 – pytest-2.3.4
collectionné 2 articles
slowTest_print.py .F
================================== FAILURES ============== ======================
____________________________________ test_2 ____________________________________
def test_2 ():
print 'in test_2'
heure (1)
print 'après 1 seconde'
heure (1)
print 'après 2 secondes'
heure (1)
print 'après 3 secondes'
> assert 0, 'échec de la démonstration'
E AssertionError: échec à des fins de démonstration
slowTest_print.py:26: AssertionError
===================== 1 échec, 1 échec en 6,01 secondes =================== ==
test_1
après 1 seconde
après 2 secondes
après 3 secondes
dans test_2
après 1 seconde
après 2 secondes
après 3 secondes
1 2 3 4 5 6 7 8 9 dix 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
> py.tester –Capturer=non slowTest_print.py ============================= tester session départs ============================== Plate-forme win32 – Python 2.7.3 – pytest–2.3.4 collecté 2 articles slowTest_print.py .F =================================== LES ÉCHECS =================================== ____________________________________ test_2 ____________________________________ def test_2(): impression 'dans test_2' temps.dormir(1) impression 'après 1 seconde' temps.dormir(1) impression 'après 2 secondes' temps.dormir(1) impression 'après 3 secondes' > affirmer 0, 'échec pour démonstration' E AssertionError: échouer pour démo fins slowTest_print.py:26: AssertionError ====================== 1 échoué, 1 passé dans 6.01 secondes ====================== test_1 après 1 seconde après 2 seconde après 3 seconde dans test_2 après 1 seconde après 2 seconde après 3 seconde |
Exemple de journalisation
La journalisation à l'aide du module de journalisation standard est gérée différemment par pytest.
Remplaçons les instructions print par des instructions de journalisation.
slowTest_log.py
temps d'importation
enregistrement des importations
logging.basicConfig (niveau = logging.DEBUG)
def test_1 ():
log = logging.getLogger ('test_1')
heure (1)
log.debug ('après 1 seconde')
heure (1)
log.debug ('après 2 secondes')
heure (1)
log.debug ('après 3 secondes')
affirmer 1, 'devrait passer'
def test_2 ():
log = logging.getLogger ('test_2')
heure (1)
log.debug ('après 1 seconde')
heure (1)
log.debug ('après 2 secondes')
heure (1)
log.debug ('après 3 secondes')
affirmer 0, 'échec pour démonstration'
1 2 3 4 5 6 7 8 9 dix 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
importation temps importation enregistrement enregistrement.basicConfig(niveau=enregistrement.DÉBOGUER) def test_1(): bûche = enregistrement.getLogger('test_1') temps.dormir(1) bûche.déboguer('après 1 seconde') temps.dormir(1) bûche.déboguer('après 2 secondes') temps.dormir(1) bûche.déboguer('après 3 secondes') affirmer 1, 'devrait passer' def test_2(): bûche = enregistrement.getLogger('test_2') temps.dormir(1) bûche.déboguer('après 1 seconde') temps.dormir(1) bûche.déboguer('après 2 secondes') temps.dormir(1) bûche.déboguer('après 3 secondes') affirmer 0, 'échec pour démonstration' |
J’ai également configuré des enregistreurs nommés distincts pour les deux tests.
L’avantage de créer des enregistreurs séparés est de rendre la sortie plus lisible.
Voici la sortie.
> py.test slowTest_log.py
============================== La session de test commence ================= ============
plate-forme win32 – Python 2.7.3 – pytest-2.3.4
collectionné 2 articles
slowTest_log.py .F
================================== FAILURES ============== ======================
____________________________________ test_2 ____________________________________
def test_2 ():
log = logging.getLogger ('test_2')
heure (1)
log.debug ('après 1 seconde')
heure (1)
log.debug ('après 2 secondes')
heure (1)
log.debug ('après 3 secondes')
> assert 0, 'échec de la démonstration'
E AssertionError: échec à des fins de démonstration
slowTest_log.py:28: AssertionError
——————————- Stderr capturé —————– —————
DEBUG: test_2: après 1 seconde
DEBUG: test_2: après 2 secondes
DEBUG: test_2: après 3 secondes
====================== 1 échec, 1 échec en 6,03 secondes ==================== ==
1 2 3 4 5 6 7 8 9 dix 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
> py.tester slowTest_log.py ============================= tester session départs ============================== Plate-forme win32 – Python 2.7.3 – pytest–2.3.4 collecté 2 articles slowTest_log.py .F =================================== LES ÉCHECS =================================== ____________________________________ test_2 ____________________________________ def test_2(): bûche = enregistrement.getLogger('test_2') temps.dormir(1) bûche.déboguer('après 1 seconde') temps.dormir(1) bûche.déboguer('après 2 secondes') temps.dormir(1) bûche.déboguer('après 3 secondes') > affirmer 0, 'échec pour démonstration' E AssertionError: échouer pour démo fins slowTest_log.py:28: AssertionError –––––––––––––––– Capturé stderr –––––––––––––––– DÉBOGUER:test_2:après 1 seconde DÉBOGUER:test_2:après 2 seconde DÉBOGUER:test_2:après 3 seconde ====================== 1 échoué, 1 passé dans 6.03 secondes ====================== |
Eh bien, la sortie est un peu plus agréable à lire.
Cependant, pytest capture toujours la sortie des enregistreurs qui écrivent sur stdout.
Notre sortie attend toujours les 6 secondes complètes avant que quelque chose ne soit signalé, et les tests réussis ne signalent pas la sortie de leur journal.
Utiliser la journalisation et désactiver la capture fait le tour, presque
Si nous utilisons à la fois la journalisation et le drapeau ‘-s’ (ou ‘–capture = no’), nous obtenons exactement ce que je veux.
Presque.
> py.test -s slowTest_log.py
============================== La session de test commence ================= ============
plate-forme win32 – Python 2.7.3 – pytest-2.3.4
collectionné 2 articles
slowTest_log.py DEBUG: test_1: après 1 seconde
DEBUG: test_1: après 2 secondes
DEBUG: test_1: après 3 secondes
.DEBUG: test_2: après 1 seconde
DEBUG: test_2: après 2 secondes
DEBUG: test_2: après 3 secondes
F
================================== FAILURES ============== ======================
____________________________________ test_2 ____________________________________
def test_2 ():
log = logging.getLogger ('test_2')
heure (1)
log.debug ('après 1 seconde')
heure (1)
log.debug ('après 2 secondes')
heure (1)
log.debug ('après 3 secondes')
> assert 0, 'échec de la démonstration'
E AssertionError: échec à des fins de démonstration
slowTest_log.py:28: AssertionError
====================== 1 échec, 1 échec en 6,02 secondes ==================== ==
1 2 3 4 5 6 7 8 9 dix 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
> py.tester –s slowTest_log.py ============================= tester session départs ============================== Plate-forme win32 – Python 2.7.3 – pytest–2.3.4 collecté 2 articles slowTest_log.py DÉBOGUER:test_1:après 1 seconde DÉBOGUER:test_1:après 2 seconde DÉBOGUER:test_1:après 3 seconde .DÉBOGUER:test_2:après 1 seconde DÉBOGUER:test_2:après 2 seconde DÉBOGUER:test_2:après 3 seconde F =================================== LES ÉCHECS =================================== ____________________________________ test_2 ____________________________________ def test_2(): bûche = enregistrement.getLogger('test_2') temps.dormir(1) bûche.déboguer('après 1 seconde') temps.dormir(1) bûche.déboguer('après 2 secondes') temps.dormir(1) bûche.déboguer('après 3 secondes') > affirmer 0, 'échec pour démonstration' E AssertionError: échouer pour démo fins slowTest_log.py:28: AssertionError ====================== 1 échoué, 1 passé dans 6.02 secondes ====================== |
Maintenant, la sortie sort pendant que le test est en cours. Excellent.
Qu'est-ce qui manque?
Alors, qu’en est-il du «presque»? Qu'est-ce qui manque?
Eh bien, c’est un peu difficile.
La sortie de style «… .F… F… X…» est maintenant intercalée avec la sortie de journalisation.
En fait, au début de chaque test, la sortie de journalisation est simplement ajoutée à stdout sans nouvelle ligne.
Pour être plus précis sur ce que je voudrais voir. Je vais simplement montrer ce que ma sortie idéale serait:
slowTest_log.py
DEBUG: test_1: après 1 seconde
DEBUG: test_1: après 2 secondes
DEBUG: test_1: après 3 secondes
.
DEBUG: test_2: après 1 seconde
DEBUG: test_2: après 2 secondes
DEBUG: test_2: après 3 secondes
F
slowTest_log.py DÉBOGUER:test_1:après 1 seconde DÉBOGUER:test_1:après 2 seconde DÉBOGUER:test_1:après 3 seconde . DÉBOGUER:test_2:après 1 seconde DÉBOGUER:test_2:après 2 seconde DÉBOGUER:test_2:après 3 seconde F |
La seule différence serait donc d'ajouter une nouvelle ligne avant la première instruction de journalisation pour chaque test.
Si quelqu'un qui lit ceci sait comment je peux dire au module de journalisation ou à pytest d'insérer ces nouvelles lignes, j'aimerais bien l'entendre.
[ad_2]