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+
Art*_*are 28
好的,首先,我觉得有必要指出,在原始代码中,实际上有两件事需要解决:
raw_input (输入副作用)需要被嘲笑.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 PromiseString被yield编辑,然后设置为通常print在with块内的值.
希望你能理解我写完的所有这些诡计,因为今晚我花了大约90分钟拼凑起来.我测试了所有这些代码,并验证了它都适用于Python 2.7.
我正在使用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)
刚遇到同样的问题,但我只是嘲笑了__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)