为了账号安全,请及时绑定邮箱和手机立即绑定

python模拟单元测试中的原始输入

python模拟单元测试中的原始输入

GCT1015 2019-10-11 10:06:00
假设我有以下python代码:def answer():    ans = raw_input('enter yes or no')    if ans == 'yes':        print 'you entered yes'    if ans == 'no':        print 'you entered no'我该如何为此编写单元测试?我知道我必须使用“模拟”,但我不知道如何使用。有人可以举一个简单的例子吗?
查看完整描述

3 回答

?
幕布斯7119047

TA贡献1794条经验 获得超8个赞

您不能修补输入,但是可以包装它以使用模拟.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+


查看完整回答
反对 回复 2019-10-11
?
猛跑小猪

TA贡献1858条经验 获得超8个赞

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


raw_input (输入副作用)需要被嘲笑。

print (输出副作用)需要检查。

在理想的单元测试功能中,不会有副作用。简单地通过传递参数来测试函数,然后检查其输出。但是通常我们想在像您这样的函数中测试不理想的函数IE。


那么我们该怎么办呢?好吧,在Python 3.3中,我上面列出的两个问题变得微不足道,因为该unittest模块获得了模拟和检查副作用的功能。但是,从2014年初开始,只有30%的Python程序员开始使用3.x,因此,为了使另外70%的Python程序员仍在使用2.x,我将概述一个答案。以目前的速度,直到2019年,3.x才会超过2.x,直到2027年,2.x才会消失。因此,我认为这个答案将在未来几年中有用。


我想一次解决上面列出的问题,因此我将首先将您的功能从使用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对这个问题的回答向我们展示了如何通过__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。如果字符串是可变的,那会很好,但不是。因此,做了一个小的Promise或proxy类PromiseString。它仅做两件事-允许设置一个字符串(或其他任何东西),并让我们知道它是否等于另一个字符串。阿PromiseString是yieldED,然后设置为通常将值print的内with块。


希望您能欣赏我今晚花了大约90分钟时间整理而成的所有技巧。我测试了所有这些代码,并验证了它们在Python 2.7中都可以使用。


查看完整回答
反对 回复 2019-10-11
?
胡说叔叔

TA贡献1804条经验 获得超8个赞

我正在使用Python 3.4,不得不调整以上答案。我的解决方案将通用代码排除在自定义runTest方法之外,并向您展示如何修补input()和print()。这是运行的代码:从io导入unittest从unittest.mock导入补丁导入StringIO


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()


查看完整回答
反对 回复 2019-10-11
  • 3 回答
  • 0 关注
  • 706 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信