python在单元测试中模拟raw_input
Posted
技术标签:
【中文标题】python在单元测试中模拟raw_input【英文标题】:python mocking raw input in unittests 【发布时间】:2014-01-29 13:37:45 【问题描述】:假设我有这个 python 代码:
def answer():
ans = raw_input('enter yes or no')
if ans == 'yes':
print 'you entered yes'
if ans == 'no':
print 'you entered no'
如何为此编写单元测试?我知道我必须使用“模拟”,但我不明白如何。谁能举个简单的例子?
【问题讨论】:
supply inputs to python unittests 的可能重复项 我在那里找不到答案 三个答案之一是字面意思关于使用mock
测试raw_input
【参考方案1】:
我正在使用 Python 3.4,并且必须调整上面的答案。我的解决方案将通用代码分解到自定义 runTest
方法中,并向您展示如何修补 input()
和 print()
。这是广告中的代码:
import unittest
from io import StringIO
from unittest.mock import patch
def answer():
ans = input('enter yes or no')
if ans == 'yes':
print('you entered yes')
if ans == 'no':
print('you entered no')
class MyTestCase(unittest.TestCase):
def runTest(self, given_answer, expected_out):
with patch('builtins.input', return_value=given_answer), patch('sys.stdout', new=StringIO()) as fake_out:
answer()
self.assertEqual(fake_out.getvalue().strip(), expected_out)
def testNo(self):
self.runTest('no', 'you entered no')
def testYes(self):
self.runTest('yes', 'you entered yes')
if __name__ == '__main__':
unittest.main()
【讨论】:
我使用nose2
(不直接使用unittest
)为我的设置调整了您的答案,这对我来说效果很好。需要注意的一件事是,如果将 fakeout.getvalue()
更改为 fakeout.getvalue().strip()
,则可以避免传递额外的换行符。【参考方案2】:
这是我在 Python 3 中所做的:
class MockInputFunction:
def __init__(self, return_value=None):
self.return_value = return_value
self._orig_input_fn = __builtins__['input']
def _mock_input_fn(self, prompt):
print(prompt + str(self.return_value))
return self.return_value
def __enter__(self):
__builtins__['input'] = self._mock_input_fn
def __exit__(self, type, value, traceback):
__builtins__['input'] = self._orig_input_fn
然后可以在任何上下文中使用。例如,pytest 使用普通的 assert
语句。
def func():
""" function to test """
x = input("What is x? ")
return int(x)
# to test, you could simply do:
with MockInputFunction(return_value=13):
assert func() == 13
【讨论】:
【参考方案3】:您无法修补输入,但可以将其包装以使用 mock.patch()。这是一个解决方案:
from unittest.mock import patch
from unittest import TestCase
def get_input(text):
return input(text)
def answer():
ans = get_input('enter yes or no')
if ans == 'yes':
return 'you entered yes'
if ans == 'no':
return 'you entered no'
class Test(TestCase):
# get_input will return 'yes' during this test
@patch('yourmodule.get_input', return_value='yes')
def test_answer_yes(self, input):
self.assertEqual(answer(), 'you entered yes')
@patch('yourmodule.get_input', return_value='no')
def test_answer_no(self, input):
self.assertEqual(answer(), 'you entered no')
请记住,此 sn-p 仅适用于 Python 3.3+ 版本
【讨论】:
@ArtOfWarfare mock 是python3.3 中的新功能docs.python.org/3/library/unittest.mock.html 有一个后向端口pypi.python.org/pypi/mock 您应该在答案中指定 python 版本。谢谢@gawel 您不需要包装输入。@patch('builtins.input', return_value='yes')
应该可以解决问题。
@patch('builtins.input', return_value='yes')
不是一个好主意,因为它会导致 pdb.set_trace()
崩溃。
为什么需要在def test_answer_yes(self, input):
中添加输入作为第二个参数?【参考方案4】:
刚刚遇到同样的问题,但我只是嘲笑__builtin__.raw_input
。
仅在 Python 2 上测试。pip install mock
如果您尚未安装该软件包。
from mock import patch
from unittest import TestCase
class TestAnswer(TestCase):
def test_yes(self):
with patch('__builtin__.raw_input', return_value='yes') as _raw_input:
self.assertEqual(answer(), 'you entered yes')
_raw_input.assert_called_once_with('enter yes or no')
def test_no(self):
with patch('__builtin__.raw_input', return_value='no') as _raw_input:
self.assertEqual(answer(), 'you entered no')
_raw_input.assert_called_once_with('enter yes or no')
或者,使用库genty,您可以简化这两个测试:
from genty import genty, genty_dataset
from mock import patch
from unittest import TestCase
@genty
class TestAnswer(TestCase):
@genty_dataset(
('yes', 'you entered yes'),
('no', 'you entered no'),
)
def test_answer(self, expected_input, expected_answer):
with patch('__builtin__.raw_input', return_value=expected_input) as _raw_input:
self.assertEqual(answer(), expected_answer)
_raw_input.assert_called_once_with('enter yes or no')
【讨论】:
【参考方案5】:好的,首先,我觉得有必要指出,在有问题的原始代码中,实际上有两件事需要解决:
raw_input
(输入副作用)需要被模拟。
print
(输出副作用)需要检查。
在理想的单元测试函数中,不会有副作用。只需提交参数即可测试函数,并检查其输出。但是我们经常想在像你这样的函数中测试不理想的函数,IE。
那么我们该怎么办?好吧,在 Python 3.3 中,我上面列出的两个问题都变得微不足道,因为 unittest
模块获得了模拟和检查副作用的能力。但是,截至 2014 年初,只有 30% 的 Python 程序员转向了 3.x,所以为了其他 70% 的 Python 程序员仍在使用 2.x,我将概述一个答案。按照目前的速度,3.x 直到 2019 年才会超过 2.x,而 2.x 直到 2027 年才会消失。所以我认为这个答案将在未来几年内有用。
我想一次解决上面列出的问题,因此我将首先将您的函数从使用print
作为其输出更改为使用return
。不出意外,代码如下:
def answerReturn():
ans = raw_input('enter yes or no')
if ans == 'yes':
return 'you entered yes'
if ans == 'no':
return 'you entered no'
所以我们需要做的就是模拟raw_input
。很简单——Omid Raha's answer to this very question 向我们展示了如何通过我们的模拟实现调出__builtins__.raw_input
实现来做到这一点。除了他的答案没有正确地组织成 TestCase
和函数,所以我会证明这一点。
import unittest
class TestAnswerReturn(unittest.TestCase):
def testYes(self):
original_raw_input = __builtins__.raw_input
__builtins__.raw_input = lambda _: 'yes'
self.assertEqual(answerReturn(), 'you entered yes')
__builtins__.raw_input = original_raw_input
def testNo(self):
original_raw_input = __builtins__.raw_input
__builtins__.raw_input = lambda _: 'no'
self.assertEqual(answerReturn(), 'you entered no')
__builtins__.raw_input = original_raw_input
关于 Python 命名约定的小提示 - 解析器需要但未使用的变量通常命名为 _
,就像 lambda 的未使用变量一样(通常是在这种情况下向用户显示的提示) raw_input
,如果你想知道为什么在这种情况下需要它)。
无论如何,这是混乱和多余的。所以我将通过添加contextmanager
来消除重复,这将允许简单的with
语句。
from contextlib import contextmanager
@contextmanager
def mockRawInput(mock):
original_raw_input = __builtins__.raw_input
__builtins__.raw_input = lambda _: mock
yield
__builtins__.raw_input = original_raw_input
class TestAnswerReturn(unittest.TestCase):
def testYes(self):
with mockRawInput('yes'):
self.assertEqual(answerReturn(), 'you entered yes')
def testNo(self):
with mockRawInput('no'):
self.assertEqual(answerReturn(), 'you entered no')
我认为这很好地回答了第一部分。进入第二部分 - 检查print
。我发现这更棘手 - 我很想听听是否有人有更好的答案。
无论如何,print
语句不能被覆盖,但如果您使用 print()
函数代替(您应该这样做)和from __future__ import print_function
,您可以使用以下语句:
class PromiseString(str):
def set(self, newString):
self.innerString = newString
def __eq__(self, other):
return self.innerString == other
@contextmanager
def getPrint():
promise = PromiseString()
original_print = __builtin__.print
__builtin__.print = lambda message: promise.set(message)
yield promise
__builtin__.print = original_print
class TestAnswer(unittest.TestCase):
def testYes(self):
with mockRawInput('yes'), getPrint() as response:
answer()
self.assertEqual(response, 'you entered yes')
def testNo(self):
with mockRawInput('no'), getPrint() as response:
answer()
self.assertEqual(response, 'you entered no')
这里的棘手之处在于您需要在输入with
块之前yield
做出响应。但是在调用 with
块内的 print()
之前,您无法知道该响应是什么。如果字符串是可变的,这会很好,但它们不是。所以取而代之的是一个小的承诺或代理类 - PromiseString
。它只做两件事——允许设置一个字符串(或任何东西,真的),并让我们知道它是否等于另一个字符串。 PromiseString
是 yield
ed,然后设置为 with
块中通常为 print
的值。
希望您能欣赏我写的所有这些诡计,因为我今晚花了大约 90 分钟来整理。我测试了所有这些代码,并验证它们都适用于 Python 2.7。
【讨论】:
感谢您的出色回答。 python 3 的更新,mockRawInput
上下文管理器:import builtins @contextmanager def mockRawInput(mock): original_raw_input = builtins.input builtins.input = lambda _: mock yield builtins.input = original_raw_input
【参考方案6】:
def answer():
ans = raw_input('enter yes or no')
if ans == 'yes':
return 'you entered yes'
if ans == 'no':
return 'you entered no'
def test_answer_yes():
assert(answer() == 'you entered yes')
def test_answer_no():
assert(answer() == 'you entered no')
origin_raw_input = __builtins__.raw_input
__builtins__.raw_input = lambda x: "yes"
test_answer_yes()
__builtins__.raw_input = lambda x: "no"
test_answer_no()
__builtins__.raw_input = origin_raw_input
【讨论】:
以上是关于python在单元测试中模拟raw_input的主要内容,如果未能解决你的问题,请参考以下文章