3

I've got a program that's built on functions that taks user inputs inside the functions and not parameters before the function: for example, say my function is

def my_function():
    a = input("a: ")
    b = input("b: ")
    print(a+b)

and from what I understand thus far about unit testing a function like that is harder to unit test than a function that works for example like this:

def another_function(a,b):
    return(a+b)

So how do I go about testing a function that looks like my_function, for example? It feels as if it would be easy to test manually by just entering incorrect inputs and checking for errors, but I have to write a test suite that tests all my functions automatically.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
zer
  • 51
  • 1
  • 2
  • 1
    You can patch `input` to provide whatever values you want, or set `input=input` in the function definition and manually inject something to replace the built-in `input` (see e.g. [this code](https://gist.github.com/textbook/f0560a4555ba3c6dfeaa#file-valid_input-py-L392) I tested with the latter method). – jonrsharpe May 15 '15 at 12:21
  • Ah, okay, so I could create a test method/function that has the variable 'a' predefined so it uses that in the test suite? It could for example assert if print("papajohn") is equal to my_function() with "papa" as a and "john" as b? – zer May 15 '15 at 12:25
  • Not really, you also need to patch `print` in your case, as your functions don't return anything (or, **much better**, restructure them to `return` and `print` elsewhere). You need to have a mock `stdout` then make sure that the text you were expecting got passed to it. – jonrsharpe May 15 '15 at 12:38
  • Okay, so the moral of the story is you can't really test a function without a return value? – zer May 15 '15 at 12:50
  • It's much more difficult, certainly! At least in Python 3 `print` is a function, which makes life a bit easier than with Python 2. – jonrsharpe May 15 '15 at 12:51

1 Answers1

1

Given that your function input comes from input and output goes to print, you will have to "mock" both of these functions to test my_function. For example, using simple manual mocking:

def my_function(input=input, print=print):
    a = input("a: ")
    b = input("b: ")
    print(a+b)

if __name__ == '__main__':
    inputs = ['hello', 'world']
    printed = []

    def mock_input(prompt):
        return inputs.pop(0)

    def mock_print(text):
        printed.append(text)

    my_function(mock_input, mock_print)
    assert len(inputs) == 0, 'not all input used'
    assert len(printed) == 1, '{} items printed'.format(len(printed))
    assert printed[0] == 'helloworld'

When you compare this to:

assert my_function('hello', 'world') == 'helloworld'

you can see why the latter is much preferred!

You could also use a proper mocking library to do this more neatly, without having to supply the functions as arguments; see e.g. How to supply stdin, files and environment variable inputs to Python unit tests?.

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 2
    Yeah, I see.. Maybe I should just rewrite my functions in my main program a bit so they return a value that I can use for my unit test, seeing as that little program took that much code to test. My programs are way longer than that and has several inputs along the way, so maybe that'll just be easier.. Thanks a lot for the help! – zer May 15 '15 at 12:58