python 在单元测试中模拟原始输入
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21046717/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
python mocking raw input in unittests
提问by user3156971
Suppose I have this python code:
假设我有这个 python 代码:
def answer():
ans = raw_input('enter yes or no')
if ans == 'yes':
print 'you entered yes'
if ans == 'no':
print 'you entered no'
How do I write a unittest for this ? I know i have to use 'Mock' but I don't understand how. Can anyone make some simple example ?
我如何为此编写单元测试?我知道我必须使用“模拟”,但我不明白如何使用。任何人都可以举一些简单的例子吗?
采纳答案by gawel
You can't patch input but you can wrap it to use mock.patch(). Here is a solution:
您不能修补输入,但可以将其包装起来以使用 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')
Keep in mind that this snippet will only work in Python versions 3.3+
请记住,此代码段仅适用于 Python 3.3+ 版本
回答by Omid Raha
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
回答by ArtOfWarfare
Okay, first off, I feel it's necessary to point out that in the original code in question, there are actually two things that need to be tackled:
好的,首先,我觉得有必要指出,在有问题的原始代码中,实际上有两件事需要解决:
raw_input(an input side effect) needs to be mocked.print(an output side effect) needs to be checked.
raw_input(输入副作用)需要被模拟。print(输出副作用)需要检查。
In an ideal function for unit testing, there would be no side effects. A function would simply be tested by handing in arguments and its output would be checked. But often we want to test functions which aren't ideal, IE, in functions like yours.
在单元测试的理想功能中,不会有副作用。一个函数将简单地通过传递参数来测试,它的输出将被检查。但通常我们想在像你这样的函数中测试不理想的函数,IE。
So what are we to do? Well, in Python 3.3, both of the issues I listed above became trivial because the unittestmodule gained the ability to mock and check for side effects. But, as of the start of 2014, only 30% of Python programmers had moved on to 3.x, so for the sake of the other 70% of Python programmers still using 2.x, I'll outline an answer. At the current rate, 3.x won't overtake 2.x until ~2019, and 2.x won't vanish until ~2027. So I figure this answer will be useful for several years to come.
那么我们该怎么办呢?好吧,在 Python 3.3 中,我上面列出的两个问题都变得微不足道,因为该unittest模块获得了模拟和检查副作用的能力。但是,截至 2014 年初,只有 30% 的 Python 程序员转向了 3.x,所以为了其他 70% 的 Python 程序员仍在使用 2.x,我将概述一个答案。按照目前的速度,3.x 到 2019 年才会超过 2.x,2.x 到 2027 年才会消失。所以我认为这个答案在未来几年会有用。
I want to address the issues listed above one at a time, so I'm going to initially change your function from using printas its output to using return. No surprises, here's that code:
我想一次解决一个上面列出的问题,因此我将首先将您的函数从 usingprint作为其输出更改为 using return。不出意外,代码如下:
def answerReturn():
ans = raw_input('enter yes or no')
if ans == 'yes':
return 'you entered yes'
if ans == 'no':
return 'you entered no'
So all we need to do is mock raw_input. Easy enough - Omid Raha's answer to this very questionshows us how to do that by swizzling out the __builtins__.raw_inputimplementation with our mock implementation. Except his answer wasn't properly organized into a TestCaseand functions, so I'll demonstrate that.
所以我们需要做的就是模拟raw_input。很简单 - Omid Raha 对这个问题的回答向我们展示了如何通过将__builtins__.raw_input实现与我们的模拟实现混在一起来做到这一点。除了他的答案没有正确组织成 aTestCase和函数,所以我会证明这一点。
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
Small note just on Python naming conventions - variables which are required by the parser but not used are typically named _, as in the case of the lambda's unused variable (which is normally the prompt shown to the user in the case of the raw_input, incase you're wondering why it's required at all in this case).
小记刚上Python的命名规则-这是由解析器必需的,但不能用通常的命名变量_,如在lambda未使用的变量的情况下(这是通常显示在的情况下,用户提示raw_input,柜面你”想知道为什么在这种情况下完全需要它)。
Anyways, this is messy and redundant. So I'm going to do away with the repetition by adding in a contextmanager, which will allow for simple withstatements.
无论如何,这是混乱和多余的。所以我将通过添加 a 来消除重复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')
I think that nicely answers the first part of this. On to the second part - checking print. I found this much trickier - I'd love to hear if anyone has a better answer.
我认为这很好地回答了第一部分。进入第二部分——检查print。我发现这更棘手 - 我很想听听是否有人有更好的答案。
Anyways, the printstatement can't be overridden, but if you use print()functions instead (as you should) and from __future__ import print_functionyou can use the following:
无论如何,该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')
The tricky bit here is that you need to yielda response before the withblock is entered. But you can't know what that response will be until the print()inside the withblock is called. This would be fine if strings were mutable, but they aren't. So instead a small promise or proxy class was made - PromiseString. It only does two things - allow a string (or anything, really) to be set and let us know if it's equal to a different string. A PromiseStringis yielded and then set to the value that would normally be printwithin the withblock.
这里的棘手之处在于您需要yield在输入with块之前做出响应。但是print()在with调用块内部之前,您无法知道该响应是什么。如果字符串是可变的,这会很好,但它们不是。所以取而代之的是一个小的承诺或代理类 - PromiseString。它只做两件事 - 允许设置一个字符串(或任何东西,真的)并让我们知道它是否等于不同的字符串。APromiseString被yield编辑,然后设置为通常print在with块内的值。
Hopefully you appreciate all this trickery I've written up since it took me around 90 minutes to put together this evening. I tested all of this code and verified it all worked with Python 2.7.
希望你能欣赏我写的所有这些诡计,因为我今晚花了大约 90 分钟才整理好。我测试了所有这些代码并验证它们都适用于 Python 2.7。
回答by Jeff-Meadows
Just ran across the same problem, but I just mocked out __builtin__.raw_input.
刚刚遇到了同样的问题,但我只是嘲笑了__builtin__.raw_input.
Only tested on Python 2. pip install mockif you don't already have the package installed.
仅在 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')
Alternatively, using the library genty, you can simplify the two tests:
或者,使用库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')
回答by tbc0
I am using Python 3.4 and had to adapt answers above. My solution factors out common code into the custom runTestmethod and shows you how to patch both input()and print(). Here's code that runs:
import unittest
from io import StringIO
from unittest.mock import patch
我正在使用 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()
回答by Kris
Here's what I do in Python 3:
这是我在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
which can then be used in any context. For instance, pytestuses ordinary assertstatements.
然后可以在任何上下文中使用。例如,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

