J'écris des tests pour une fonction comme la suivante:
def foo():
print 'hello world!'
Donc, quand je veux tester cette fonction, le code sera comme ceci:
import sys
from foomodule import foo
def test_foo():
foo()
output = sys.stdout.getline().strip() # because stdout is an StringIO instance
assert output == 'hello world!'
Mais si j'exécute nosetests avec le paramètre -s, le test se bloque. Comment puis-je attraper la sortie avec le module unittest ou nose?
J'utilise ceci gestionnaire de contexte pour capturer la sortie. Il utilise finalement la même technique que certaines des autres réponses en remplaçant temporairement sys.stdout
. Je préfère le gestionnaire de contexte car il encapsule toute la comptabilité dans une seule fonction, donc je n'ai pas à réécrire de code try-finally, et je n'ai pas à écrire de fonctions de configuration et de démontage juste pour cela.
import sys
from contextlib import contextmanager
from StringIO import StringIO
@contextmanager
def captured_output():
new_out, new_err = StringIO(), StringIO()
old_out, old_err = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = new_out, new_err
yield sys.stdout, sys.stderr
finally:
sys.stdout, sys.stderr = old_out, old_err
Utilisez-le comme ceci:
with captured_output() as (out, err):
foo()
# This can go inside or outside the `with` block
output = out.getvalue().strip()
self.assertEqual(output, 'hello world!')
De plus, étant donné que l'état de sortie d'origine est restauré à la sortie du bloc with
, nous pouvons configurer un deuxième bloc de capture dans la même fonction que le premier, ce qui n'est pas possible à l'aide des fonctions de configuration et de démontage, et obtient verbeux lors de l'écriture manuelle des blocs try-finally. Cette capacité s'est avérée utile lorsque l'objectif d'un test était de comparer les résultats de deux fonctions l'une par rapport à l'autre plutôt qu'à une valeur précalculée.
Si vous voulez vraiment le faire, vous pouvez réaffecter sys.stdout pour la durée du test.
def test_foo():
import sys
from foomodule import foo
from StringIO import StringIO
saved_stdout = sys.stdout
try:
out = StringIO()
sys.stdout = out
foo()
output = out.getvalue().strip()
assert output == 'hello world!'
finally:
sys.stdout = saved_stdout
Si j'écrivais ce code, cependant, je préférerais passer un paramètre out
facultatif à la fonction foo
.
def foo(out=sys.stdout):
out.write("hello, world!")
Ensuite, le test est beaucoup plus simple:
def test_foo():
from foomodule import foo
from StringIO import StringIO
out = StringIO()
foo(out=out)
output = out.getvalue().strip()
assert output == 'hello world!'
Depuis la version 2.7, vous n'avez plus besoin de réaffecter sys.stdout
, ceci est fourni par buffer
flag . De plus, c'est le comportement par défaut de nosetest.
Voici un exemple d'échec dans un contexte non tamponné:
import sys
import unittest
def foo():
print 'hello world!'
class Case(unittest.TestCase):
def test_foo(self):
foo()
if not hasattr(sys.stdout, "getvalue"):
self.fail("need to run in buffered mode")
output = sys.stdout.getvalue().strip() # because stdout is an StringIO instance
self.assertEquals(output,'hello world!')
Vous pouvez définir un tampon via unit2
indicateur de ligne de commande -b
, --buffer
ou dans unittest.main
options. L'inverse est obtenu grâce à nosetest
flag --nocapture
.
if __name__=="__main__":
assert not hasattr(sys.stdout, "getvalue")
unittest.main(module=__name__, buffer=True, exit=False)
#.
#----------------------------------------------------------------------
#Ran 1 test in 0.000s
#
#OK
assert not hasattr(sys.stdout, "getvalue")
unittest.main(module=__name__, buffer=False)
#hello world!
#F
#======================================================================
#FAIL: test_foo (__main__.Case)
#----------------------------------------------------------------------
#Traceback (most recent call last):
# File "test_stdout.py", line 15, in test_foo
# self.fail("need to run in buffered mode")
#AssertionError: need to run in buffered mode
#
#----------------------------------------------------------------------
#Ran 1 test in 0.002s
#
#FAILED (failures=1)
Beaucoup de ces réponses ont échoué pour moi parce que vous ne pouvez pas from StringIO import StringIO
in Python 3. Voici un extrait de travail minimum basé sur le commentaire de @ naxa et le Python Cookbook.
from io import StringIO
from unittest.mock import patch
with patch('sys.stdout', new=StringIO()) as fakeOutput:
print('hello world')
self.assertEqual(fakeOutput.getvalue().strip(), 'hello world')
Dans python 3.5 vous pouvez utiliser contextlib.redirect_stdout()
et StringIO()
. Voici la modification de votre code
import contextlib
from io import StringIO
from foomodule import foo
def test_foo():
temp_stdout = StringIO()
with contextlib.redirect_stdout(temp_stdout):
foo()
output = temp_stdout.getvalue().strip()
assert output == 'hello world!'
J'apprends juste Python et je me suis retrouvé aux prises avec un problème similaire à celui ci-dessus avec des tests unitaires pour les méthodes avec sortie. Mon test unitaire réussi pour le module foo ci-dessus a fini par ressembler à ceci :
import sys
import unittest
from foo import foo
from StringIO import StringIO
class FooTest (unittest.TestCase):
def setUp(self):
self.held, sys.stdout = sys.stdout, StringIO()
def test_foo(self):
foo()
self.assertEqual(sys.stdout.getvalue(),'hello world!\n')
L'écriture de tests nous montre souvent une meilleure façon d'écrire notre code. Semblable à la réponse de Shane, j'aimerais suggérer une autre façon de voir les choses. Voulez-vous vraiment affirmer que votre programme a sorti une certaine chaîne, ou simplement qu'il a construit ne certaine chaîne pour la sortie? Cela devient plus facile à tester, car nous pouvons probablement supposer que l'instruction Python print
fait correctement son travail.
def foo_msg():
return 'hello world'
def foo():
print foo_msg()
Votre test est alors très simple:
def test_foo_msg():
assert 'hello world' == foo_msg()
Bien sûr, si vous avez vraiment besoin de tester la sortie réelle de votre programme, n'hésitez pas à l'ignorer. :)
Sur la base de la réponse de Rob Kennedy, j'ai écrit une version basée sur les classes du gestionnaire de contexte pour tamponner la sortie.
L'utilisation est comme:
with OutputBuffer() as bf:
print('hello world')
assert bf.out == 'hello world\n'
Voici l'implémentation:
from io import StringIO
import sys
class OutputBuffer(object):
def __init__(self):
self.stdout = StringIO()
self.stderr = StringIO()
def __enter__(self):
self.original_stdout, self.original_stderr = sys.stdout, sys.stderr
sys.stdout, sys.stderr = self.stdout, self.stderr
return self
def __exit__(self, exception_type, exception, traceback):
sys.stdout, sys.stderr = self.original_stdout, self.original_stderr
@property
def out(self):
return self.stdout.getvalue()
@property
def err(self):
return self.stderr.getvalue()
Ou pensez à utiliser pytest
, il a un support intégré pour affirmer stdout et stderr. Voir docs
def test_myoutput(capsys): # or use "capfd" for fd-level
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
print("next")
captured = capsys.readouterr()
assert captured.out == "next\n"