run - ¿Cómo afirmar el resultado con nosetest/unittest en python?
unit test en python (9)
Estoy escribiendo pruebas para una función como la siguiente:
def foo():
print ''hello world!''
Entonces, cuando quiera probar esta función, el código será así:
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!''
Pero si ejecuto nosetests con el parámetro -s, la prueba se bloquea. ¿Cómo puedo capturar la salida con unittest o módulo de nariz?
Basado en la respuesta de Rob Kennedy, escribí una versión basada en clases del administrador de contexto para almacenar la salida.
El uso es como:
with OutputBuffer() as bf:
print(''hello world'')
assert bf.out == ''hello world/n''
Aquí está la implementación:
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()
Desde la versión 2.7, ya no es necesario reasignar sys.stdout
, esto se proporciona a través del indicador de buffer
. Además, es el comportamiento predeterminado de nosetest.
Aquí hay un ejemplo de error en el contexto no almacenado en el búfer:
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!'')
Puede configurar el búfer a través de la línea de comando de la --buffer
, indicador -b
, --buffer
o en unittest.main
opciones. Lo opuesto se logra a través de la bandera de --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)
En Python 3.5 puede usar contextlib.redirect_stdout()
y StringIO()
. Aquí está la modificación de tu código
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!''
Las pruebas de escritura a menudo nos muestran una mejor manera de escribir nuestro código. Al igual que la respuesta de Shane, me gustaría sugerir otra forma de ver esto. ¿Realmente desea afirmar que su programa generó una determinada cadena, o simplemente que construyó una determinada cadena para la salida? Esto se vuelve más fácil de probar, ya que probablemente podamos suponer que la declaración de print
Python hace su trabajo correctamente.
def foo_msg():
return ''hello world''
def foo():
print foo_msg()
Entonces tu prueba es muy simple:
def test_foo_msg():
assert ''hello world'' == foo_msg()
Por supuesto, si realmente necesita probar el rendimiento real de su programa, no dude en ignorarlo. :)
Muchas de estas respuestas me fallaron porque no puedes from StringIO import StringIO
en Python 3. Aquí hay un fragmento de trabajo mínimo basado en el comentario de @ naxa y en el 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'')
O considere usar pytest
, tiene soporte incorporado para afirmar stdout y stderr. Ver 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"
Recién estoy aprendiendo Python y me encontré luchando con un problema similar al anterior con pruebas unitarias de métodos con salida. Mi prueba de unidad de paso para el módulo foo anterior ha terminado pareciéndose a esto:
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'')
Si realmente desea hacer esto, puede reasignar sys.stdout durante la prueba.
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
Sin embargo, si estuviera escribiendo este código, preferiría pasar un parámetro de out
opcional a la función foo
.
def foo(out=sys.stdout):
out.write("hello, world!")
Entonces la prueba es mucho más 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!''
Uso este administrador de contexto para capturar resultados. En última instancia, utiliza la misma técnica que algunas de las otras respuestas mediante el reemplazo temporal de sys.stdout
. Prefiero el administrador de contexto porque envuelve toda la contabilidad en una sola función, por lo que no tengo que volver a escribir ningún código try-finally, y no tengo que escribir funciones de configuración y eliminación solo para esto.
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
Úselo así:
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!'')
Además, dado que el estado de salida original se restablece al salir del bloque with
, podemos configurar un segundo bloque de captura en la misma función que el primero, lo que no es posible con las funciones de configuración y desmontaje, y se vuelve prolijo cuando se escribe try- finalmente bloquea manualmente. Esa capacidad fue útil cuando el objetivo de una prueba era comparar los resultados de dos funciones entre sí en lugar de un valor precalculado.