python模拟unittests中的原始输入

use*_*971 33 python unit-testing mocking

假设我有这个python代码:

def answer():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        print 'you entered yes'
    if ans == 'no':
        print 'you entered no'
Run Code Online (Sandbox Code Playgroud)

我该如何为此编写单元测试?我知道我必须使用'模拟',但我不明白如何.有谁可以做一些简单的例子?

gaw*_*wel 41

您无法修改输入,但可以将其包装为使用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')
Run Code Online (Sandbox Code Playgroud)

请记住,此代码段仅适用于Python版本3.3+

  • @ArtOfWarfare模拟是python3.3中的新功能https://docs.python.org/3/library/unittest.mock.html有一个backport https://pypi.python.org/pypi/mock (8认同)
  • 您不需要包装输入.`@patch('builtins.input',return_value ='yes')`应该做的伎俩. (4认同)

Art*_*are 28

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

  1. raw_input (输入副作用)需要被嘲笑.
  2. print (需要检查输出副作用).

在单元测试的理想功能中,没有副作用.只需通过处理参数来测试函数,就会检查其输出.但是我们经常想在像你这样的函数中测试不理想的函数IE.

那么我们该怎么办呢?好吧,在Python 3.3中,我上面列出的两个问题都变得微不足道,因为unittest模块获得了模拟和检查副作用的能力.但是,截至2014年初,只有30%的Python程序员已经转向3.x,所以为了其他70%的Python程序员仍然使用2.x,我将概述一个答案.按照目前的速度,3.x直到~209才会超过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'
Run Code Online (Sandbox Code Playgroud)

所以我们需要做的就是嘲笑raw_input.很容易--Omid Raha对这个问题的回答告诉我们如何通过__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
Run Code Online (Sandbox Code Playgroud)

关于Python命名约定的小注释 - 解析器所需但未使用的变量通常被命名_,就像lambda的未使用变量一样(在通常情况下向用户显示的提示raw_input,包括你的'想知道为什么在这种情况下根本需要它).

无论如何,这是混乱和多余的.因此,我将通过添加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')
Run Code Online (Sandbox Code Playgroud)

我认为很好地回答了第一部分.到第二部分 - 检查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')
Run Code Online (Sandbox Code Playgroud)

这里棘手的一点是你需要yield在输入with块之前做出响应.但是,print()with调用块内部之前,您无法知道响应是什么.如果字符串是可变的,这将是好的,但它们不是.因此,制作了一个小承诺或代理类 - PromiseString.它只做两件事 - 允许设置一个字符串(或任何东西,真的),让我们知道它是否等于不同的字符串.A PromiseStringyield编辑,然后设置为通常printwith块内的值.

希望你能理解我写完的所有这些诡计,因为今晚我花了大约90分钟拼凑起来.我测试了所有这些代码,并验证了它都适用于Python 2.7.


tbc*_*bc0 8

我正在使用Python 3.4并且必须调整上面的答案.我的解决方案将常用代码纳入自定义runTest方法,并向您展示如何修补input()print().这是运行的代码:从unittest.mock导入补丁的io import StringIO导入unittest

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()
Run Code Online (Sandbox Code Playgroud)


Jef*_*ows 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')
Run Code Online (Sandbox Code Playgroud)

或者,使用库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')
Run Code Online (Sandbox Code Playgroud)