假设我有这个python代码:
def answer():
ans = raw_input('enter yes or no')
if ans == 'yes':
print 'you entered yes'
if ans == 'no':
print 'you entered no'
我如何为此编写单元测试?我知道我必须使用'模拟',但我不明白如何。有人可以做一些简单的例子吗?
答案 0 :(得分:36)
您无法修改输入,但可以将其包装为使用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')
请注意,此代码段仅适用于Python版本3.3 +
答案 1 :(得分:28)
好的,首先,我觉得有必要指出在原始代码中,实际上有两件事需要解决:
raw_input
(输入副作用)需要被模拟。print
(输出副作用)需要进行检查。在理想的单元测试功能中,没有副作用。只需通过处理参数来测试函数,就会检查其输出。但是我们常常想要在像你这样的函数中测试不理想的函数IE。
那我们该怎么办?好吧,在Python 3.3中,我上面列出的两个问题变得微不足道,因为unittest
模块获得了模拟和检查副作用的能力。但是,截至2014年初,只有30%的Python程序员已经转向3.x,所以为了其他70%的Python程序员仍然使用2.x,我将概述一个答案。按照目前的速度,3.x不会超过2.x直到~209,并且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')
这里棘手的一点是,在输入yield
块之前,您需要with
响应。但是,在调用print()
块中的with
之前,您无法知道该响应是什么。如果字符串是可变的,这将是好的,但它们不是。因此,提出了一个小承诺或代理类 - PromiseString
。它只做两件事 - 允许设置一个字符串(或任何东西,真的),让我们知道它是否等于不同的字符串。 PromiseString
为yield
,然后设置为print
块中通常为with
的值。
希望你能理解我写的所有这些诡计,因为我今晚花了大约90分钟才把它拼凑起来。我测试了所有这些代码,并验证了它都适用于Python 2.7。
答案 2 :(得分:7)
我正在使用Python 3.4并且必须调整上面的答案。我的解决方案将常用代码纳入自定义runTest
方法,并向您展示如何修补input()
和print()
。这里运行的代码如下:
进口单位测试
来自io import StringIO
来自unittest.mock导入补丁
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()
答案 3 :(得分:6)
刚遇到同样的问题,但我只是嘲笑__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')
答案 4 :(得分:0)
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
答案 5 :(得分:0)
这是我在 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