There is no obvious way to test your function as it is currently written, because you are reading from standard input. This is a side effect that makes your function non-pure and hard to test. In other words, the input comes from outside your program, so you do not have control over it.
You should rewrite your function in a way that makes it easy to provide input and check output within the program. You can do this passing a function to use as a way to get input instead of always using input(). Then, for testing, you can create a simple iterator object over a list of known inputs and pass its __next__ method as input function.
Here's an example of how this could be done:
def get_inputs(input_function):
str_from_user = input_function()
list_of_marks = []
while str_from_user != "exit":
list_of_marks.append('!')
str_from_user = input_function()
return list_of_marks
def test_get_inputs():
input_func_one = iter(["hey", "coco", "milo", "exit"]).__next__
input_func_two = iter(["exit"]).__next__
input_func_three = iter(["horses have long faces", "exit"]).__next__
assert get_inputs(input_func_one) == ['!', '!', '!']
assert get_inputs(input_func_two) == []
assert get_inputs(input_func_three) == ['!']
Another option, as suggested by @chepner in the above comments, would be to pass the function a file-like object, which can be easily mocked using the io module with either BytesIO or StringIO. Take care in this case though because input() strips the trailing newline, but StringIO.readline() does not.
from io import StringIO
def get_inputs(input_file):
str_from_user = input_file.readline().rstrip()
list_of_marks = []
while str_from_user != "exit":
list_of_marks.append('!')
str_from_user = input_file.readline().rstrip()
return list_of_marks
def test_get_inputs():
assert get_inputs(StringIO("hey\ncoco\nmilo\nexit\n")) == ['!', '!', '!']
assert get_inputs(StringIO("exit\n")) == []
assert get_inputs(StringIO("horses have long faces\nexit\n")) == ['!']
You could also adapt the first case (passing an input function) to work with StringIO by simply passing StringIO("...").readline as function.
Now that you have a function that is easily testable, you can write a simple wrapper that uses the function and works with standard input instead:
# If using an input function
def get_inputs_from_stdin():
return get_inputs(input)
# If using a file-like object
import sys
def get_inputs_from_stdin():
return get_inputs(sys.stdin)
inputfunction.mocking but I have only worked withunittestso not sure how it works withpytest. Though you should rethink the design of the tests.get_inputsa file-like object parameterfthat defaults tosys.stdin, then usef.readline()instead ofinputto read the input. Then you can test your function with aio.StringIOargument instead of mocking anything. Your production code can continue to call the function with no explicit argument to read from standard input.