You can use unittest to mock the user's input:
import builtins from unittest.mock import patch def func(): while True: char = input().lower() if char == 'q': break elif char.isalpha(): print("It's not a number") if int(char) == 3: break else: print('Try again') def test_func(inputs): with patch("builtins.input") as input_mock: input_mock.side_effect = inputs func() test_func(["q"]) test_func(["3"]) test_func(["4", "5", "6"]) # StopIteration error indicates this input is not sufficient for the function to return test_func(["a", "b", "c", "q"]) # ValueError indicates a bug in the function test_func(["4", "5", "6", "q"]) # Try again 3 times as expected
EDIT: You could also use unittest to capture the printed output and return it, so that you can check this output systematically against the expected output.
import builtins from unittest.mock import patch import io def test_func(inputs): with patch("builtins.input") as input_mock, \ patch("sys.stdout", new_callable=io.StringIO) as output_mock: input_mock.side_effect = inputs try: func() except StopIteration: print("FUNCTION DID NOT RETURN") return output_mock.getvalue().strip().split("\n") print(test_func(["q"]) == [""]) # True print(test_func(["4", "5", "6", "q"]) == ['Try again', 'Try again', 'Try again']) # True