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