I'm trying to test a function that takes input from stdin
, which I'm currently testing with something like this:
cat /usr/share/dict/words | ./spellchecker.py
In the name of test automation, is there any way that pyunit
can fake input to raw_input()
?
The short answer is to monkey patch raw_input()
.
There are some good examples in the answer to How to display the redirected stdin in Python?
Here is a simple, trivial example using a lambda
that throws away the prompt and returns what we want.
System Under Test
cat ./name_getter.py
#!/usr/bin/env python
class NameGetter(object):
def get_name(self):
self.name = raw_input('What is your name? ')
def greet(self):
print 'Hello, ', self.name, '!'
def run(self):
self.get_name()
self.greet()
if __name__ == '__main__':
ng = NameGetter()
ng.run()
$ echo Derek | ./name_getter.py
What is your name? Hello, Derek !
Test case:
$ cat ./t_name_getter.py
#!/usr/bin/env python
import unittest
import name_getter
class TestNameGetter(unittest.TestCase):
def test_get_alice(self):
name_getter.raw_input = lambda _: 'Alice'
ng = name_getter.NameGetter()
ng.get_name()
self.assertEquals(ng.name, 'Alice')
def test_get_bob(self):
name_getter.raw_input = lambda _: 'Bob'
ng = name_getter.NameGetter()
ng.get_name()
self.assertEquals(ng.name, 'Bob')
if __name__ == '__main__':
unittest.main()
$ ./t_name_getter.py -v
test_get_alice (__main__.TestNameGetter) ... ok
test_get_bob (__main__.TestNameGetter) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Update -- using unittest.mock.patch
Since python 3.3 there is new submodule for unittest
called mock that does exactly what you need to do. For those using python 2.6 or above there is a backport of mock
found here.
import unittest
from unittest.mock import patch
import module_under_test
class MyTestCase(unittest.TestCase):
def setUp(self):
# raw_input is untouched before test
assert module_under_test.raw_input is __builtins__.raw_input
def test_using_with(self):
input_data = "123"
expected = int(input_data)
with patch.object(module_under_test, "raw_input", create=True,
return_value=expected):
# create=True is needed as raw_input is not in the globals of
# module_under_test, but actually found in __builtins__ .
actual = module_under_test.function()
self.assertEqual(expected, actual)
@patch.object(module_under_test, "raw_input", create=True)
def test_using_decorator(self, raw_input):
raw_input.return_value = input_data = "123"
expected = int(input_data)
actual = module_under_test.function()
self.assertEqual(expected, actual)
def tearDown(self):
# raw input is restored after test
assert module_under_test.raw_input is __builtins__.raw_input
if __name__ == "__main__":
unittest.main()
# where module_under_test.function is:
def function():
return int(raw_input("prompt> "))
Previous answer -- replacing sys.stdin
I think the sys module might be what you're looking for.
You can do something like
import sys
# save actual stdin in case we need it again later
stdin = sys.stdin
sys.stdin = open('simulatedInput.txt','r')
# or whatever else you want to provide the input eg. StringIO
raw_input will now read from simulatedInput.txt whenever it is called. If the contents of simulatedInput was
hello
bob
then the first call to raw_input would return "hello", the second "bob" and third would throw an EOFError as there was no more text to read.
Replace sys.stdin
with an instance of StringIO
, and load the StringIO
instance with the data you want returned via sys.stdin
. Also, sys.__stdin__
contains the original sys.stdin
object, so restoring sys.stdin
after your test is as simple as sys.stdin = sys.__stdin__
.
Fudge is a great python mock module, with convenient decorators for doing patching like this for you, with automatic cleanup. You should check it out.
You didn't describe what sort of code is in spellchecker.py
, which gives me freedom to speculate.
Suppose it's something like this:
import sys
def check_stdin():
# some code that uses sys.stdin
To improve testability of check_stdin
function, I propose to refactor it like so:
def check_stdin():
return check(sys.stdin)
def check(input_stream):
# same as original code, but instead of
# sys.stdin it is written it terms of input_stream.
Now most of your logic is in check
function, and you can hand-craft whatever input you can imagine in order to test it properly, without any need to deal with stdin
.
My 2 cents.
If you are using mock module (written by Michael Foord), in order to mock raw_input function you can use syntax like:
@patch('src.main.raw_input', create=True, new=MagicMock(return_value='y'))
def test_1(self):
method_we_try_to_test(); # method or function that calls **raw_input**
来源:https://stackoverflow.com/questions/6271947/how-can-i-simulate-input-to-stdin-for-pyunit