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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 21:54:13  来源:igfitidea点击:

python mocking raw input in unittests

pythonunit-testingmocking

提问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:

好的,首先,我觉得有必要指出,在有问题的原始代码中,实际上有两件事需要解决:

  1. raw_input(an input side effect) needs to be mocked.
  2. print(an output side effect) needs to be checked.
  1. raw_input(输入副作用)需要被模拟。
  2. 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。它只做两件事 - 允许设置一个字符串(或任何东西,真的)并让我们知道它是否等于不同的字符串。APromiseStringyield编辑,然后设置为通常printwith块内的值。

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