python - tutorial - Pytest: ¿Cómo probar una función con una llamada de entrada?
programar funciones en python (4)
Tengo un programa de consola escrito en Python. Le hace preguntas al usuario usando el comando:
some_input = input(''Answer the question:'', ...)
¿Cómo probaría una función que contiene una llamada a input
usando pytest
? No quisiera forzar a un probador a ingresar texto muchas veces solo para terminar una ejecución de prueba.
Como sugirió The Compiler, pytest tiene un nuevo accesorio de monkeypatch para esto. Un objeto monkeypatch puede alterar un atributo en una clase o un valor en un diccionario, y luego restaurar su valor original al final de la prueba.
En este caso, la función de input
incorporada es un valor del diccionario __builtins__
de python, por lo que podemos modificarlo así:
def test_something_that_involves_user_input(monkeypatch):
# monkeypatch the "input" function, so that it returns "Mark".
# This simulates the user entering "Mark" in the terminal:
monkeypatch.setattr(''builtins.input'', lambda x: "Mark")
# go about using input() like you normally would:
i = input("What is your name?")
assert i == "Mark"
Edición: Cambiado lambda: "Mark"
a lambda x: "Mark"
Probablemente debería burlarse de la función de input
incorporada, puede usar la funcionalidad de teardown
proporcionada por pytest
para volver a la función de input
original después de cada prueba.
import module # The module which contains the call to input
class TestClass:
def test_function_1(self):
# Override the Python built-in input method
module.input = lambda: ''some_input''
# Call the function you would like to test (which uses input)
output = module.function()
assert output == ''expected_output''
def test_function_2(self):
module.input = lambda: ''some_other_input''
output = module.function()
assert output == ''another_expected_output''
def teardown_method(self, method):
# This method is being called after each test case, and it will revert input back to original function
module.input = input
Una solución más elegante sería utilizar el módulo mock
junto con una with statement
. De esta manera, no es necesario que uses desmontaje y el método parcheado solo vivirá dentro del alcance.
import mock
import module
def test_function():
with mock.patch.object(__builtin__, ''input'', lambda: ''some_input''):
assert module.function() == ''expected_output''
Puede reemplazar sys.stdin
con algún IO de texto personalizado, como la entrada de un archivo o un búfer StringIO en memoria:
import sys
class Test:
def test_function(self):
sys.stdin = open("preprogrammed_inputs.txt")
module.call_function()
def setup_method(self):
self.orig_stdin = sys.stdin
def teardown_method(self):
sys.stdin = self.orig_stdin
esto es más robusto que solo la input()
parches input()
, ya que no será suficiente si el módulo utiliza otros métodos para consumir texto de la entrada estándar.
Esto también se puede hacer con bastante elegancia con un administrador de contexto personalizado.
import sys
from contextlib import contextmanager
@contextmanager
def replace_stdin(target):
orig = sys.stdin
sys.stdin = target
yield
sys.stdin = orig
Y luego usarlo así, por ejemplo:
with replace_stdin(StringIO("some preprogrammed input")):
module.call_function()
Puedes hacerlo con mock.patch
siguiente manera.
Primero, en su código, cree una función ficticia para que las llamadas input
:
def __get_input(text):
return input(text)
En tus funciones de prueba:
import my_module
from mock import patch
@patch(''my_module.__get_input'', return_value=''y'')
def test_what_happens_when_answering_yes(self, mock):
"""
Test what happens when user input is ''y''
"""
# whatever your test function does
Por ejemplo, si tiene un bucle que comprueba que las únicas respuestas válidas están en [''y'', ''Y'', ''n'', ''N''], puede probar que no pasa nada al ingresar un valor diferente.
En este caso, asumimos que se
SystemExit
unSystemExit
al responder ''N'':
@patch(''my_module.__get_input'')
def test_invalid_answer_remains_in_loop(self, mock):
"""
Test nothing''s broken when answer is not [''Y'', ''y'', ''N'', ''n'']
"""
with self.assertRaises(SystemExit):
mock.side_effect = [''k'', ''l'', ''yeah'', ''N'']
# call to our function asking for input