I have a console program written in Python. It asks the user questions using the command:
some_input = input('Answer the question:', ...)
How would I test a function containing a call to input
using pytest
? I wouldn't want to force a tester to input text many many times only to finish one test run.
4 Answers
Answers 1
You should probably mock the built-in input
function, you can use the teardown
functionality provided by pytest
to revert back to the original input
function after each test.
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
A more elegant solution would be to use the mock
module together with a with statement
. This way you don't need to use teardown and the patched method will only live within the with
scope.
import mock import module def test_function(): with mock.patch.object(__builtin__, 'input', lambda: 'some_input'): assert module.function() == 'expected_output'
Answers 2
As The Compiler suggested, pytest has a new monkeypatch fixture for this. A monkeypatch object can alter an attribute in a class or a value in a dictionary, and then restore its original value at the end of the test.
In this case, the built-in input
function is a value of python's __builtins__
dictionary, so we can alter it like so:
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.setitem(__builtins__, 'input', lambda: "Mark") # go about using input() like you normally would: i = input("What is your name?") assert i == "Mark"
Answers 3
You can do it with mock.patch
as follows.
First, in your code, create a dummy function for the calls to input
:
def __get_input(text): return input(text)
In your test functions:
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
For example if you've a loop checking that the only valid answers are ['y', 'Y', 'n', 'N'] you can test that nothing happens when entering a different value instead.
In this case we assume a
SystemExit
is raised when answering '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
Answers 4
You can replace sys.stdin
with some custom Text IO, like input from a file or an in-memory StringIO buffer:
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
this is more robust than only patching input()
, as that won't be sufficient if the module uses any other methods of consuming text from stdin.
0 comments:
Post a Comment