Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3 unittest simulate user input

How can I simulate user input in the middle of a function called by a unit test (Using Python 3's unittest)? For example, I have a function foo() who's output I'm testing. In the foo() function, it asks for user input:

x = input(msg)

And the output is based on the input:

print("input: {0}".format(x))

I would like my unit test to run foo(), enter an input and compare the result with the expected result.

like image 538
royvandewater Avatar asked Aug 25 '09 15:08

royvandewater


2 Answers

Having difficulties in testing some component because of dependencies it's usually a sign of bad design. Your foo function should not depend on the global input function, but rather on a parameter. Then, when you run the program in a production environment, you wire the things in such a way that the foo is called with the result of what input returns. So foo should read:

def foo(input):
    # do something with input

This will make testing much more easier. And by the way, if your tests have IO dependencies they're no longer unit tests, but rather functional tests. Have a look on Misko Hevery's blog for more insights into testing.

like image 41
Ionuț G. Stan Avatar answered Oct 12 '22 22:10

Ionuț G. Stan


I have this problem regularly when I'm trying to test code that brings up dialogs for user input, and the same solution should work for both. You need to provide a new function bound to the name input in your test scope with the same signature as the standard input function which just returns a test value without actually prompting the user. Depending on how your tests and code are setup this injection can be done in a number of ways, so I'll leave that as an exercise for the reader, but your replacement method will be something simple like:

def my_test_input(message):
  return 7

Obviously you could also switch on the contents of message if that were relevant, and return the datatype of your choice of course. You can also do something more flexible and general that allows for reusing the same replacement method in a number of situations:

def my_test_input(retval, message):
  return retval

and then you would inject a partial function into input:

import functools
test_input_a = functools.partial(my_test_input, retval=7)
test_input_b = functools.partial(my_test_input, retval="Foo")

Leaving test_input_a and test_input_b as functions that take a single message argument, with the retval argument already bound.

like image 145
Nick Bastin Avatar answered Oct 13 '22 00:10

Nick Bastin