我正在尝试测试一个从 stdin
获取输入的函数,目前我正在使用以下方式进行测试:
cat /usr/share/dict/words | ./spellchecker.py
为了进行测试自动化,有没有办法让pyunit
模拟用户输入到raw_input()
中?
我正在尝试测试一个从 stdin
获取输入的函数,目前我正在使用以下方式进行测试:
cat /usr/share/dict/words | ./spellchecker.py
为了进行测试自动化,有没有办法让pyunit
模拟用户输入到raw_input()
中?
简短回答是通过Monkey Patch来修改raw_input()
函数。
在How to display the redirected stdin in Python?问题的答案中有一些很好的例子。
下面是一个简单的、微不足道的例子,使用了一个lambda
来丢弃提示并返回我们想要的结果。
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 !
$ 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
name_getter.raw_input
在 NameGetter
中定义在哪里? - Alpername_getter
模块进行了猴子补丁,修改了内置函数 raw_input()
。 - johnsyweb自Python 3.3以来,有一个新的子模块unittest
被称为mock,可以完全满足您的需求。对于使用Python 2.6或更高版本的用户,可以在此处找到mock
的后移版本。
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> "))
我认为你可能需要使用 sys 模块。
你可以像这样做:
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函数都将从simulatedInput.txt中读取内容。如果simulatedInput的内容为
hello
bob
那么第一次调用raw_input会返回"hello",第二次会返回"bob",第三次由于没有更多的文本可读,将抛出EOFError。
stringIO
代替真实文件会更好吗?如果是的话,怎样更好呢?(除了比磁盘IO更快之外,但数据迟早都来自磁盘,这样你可以将测试数据与测试代码分开。请注意不要改变原意。) - tedsys.stdin
和open()
返回的流并不完全相同。例如,如果您的代码执行stream.tell()
,那么它将在此测试中工作,但在真正的sys.stdin
中则不会。 - danvkraw_input
现在已经从Python API中删除,并被input
所取代。 - Iker Jimenezspellchecker.py
中的代码类型,这使我可以自由推测。import sys
def check_stdin():
# some code that uses sys.stdin
check_stdin
函数的测试性,我建议将其重构如下: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.
check
函数中,你可以手工制作任何输入来测试它,而不需要处理 stdin
。将sys.stdin
替换为StringIO
的实例,并通过sys.stdin
加载StringIO
实例中的数据。此外,sys.__stdin__
包含原始的sys.stdin
对象,因此在测试后恢复sys.stdin
就像sys.stdin = sys.__stdin__
这样简单。
Fudge是一个很棒的Python模拟模块,具有方便的装饰器,可以为您执行类似于补丁的操作,并自动清理。你应该去看一下。
sys.stdin
,则很有可能您正在尝试进行过多的“单元测试”。关于Fudge,请考虑自己实现模拟的好处。 - johnsyweb如果您正在使用mock模块(由Michael Foord编写),为了模拟raw_input函数,您可以使用以下语法:
@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**
./spellchecker.py < /usr/share/dict/words
) - johnsyweb