una tutorial programar otro otra mandar llamar lista funciones funcion desde dentro crear como clase archivo python unit-testing testing input pytest

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 un SystemExit 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