好的,首先,我觉得有必要指出,在有问题的原始代码中,实际上有两件事需要解决:
-
raw_input(输入副作用)需要被模拟。
-
print(输出副作用)需要检查。
在理想的单元测试函数中,不会有副作用。只需提交参数即可测试函数,并检查其输出。但是我们经常想在像你这样的函数中测试不理想的函数,IE。
那么我们该怎么办?好吧,在 Python 3.3 中,我上面列出的两个问题都变得微不足道,因为 unittest 模块获得了模拟和检查副作用的能力。但是,截至 2014 年初,只有 30% 的 Python 程序员转向了 3.x,所以为了其他 70% 的 Python 程序员仍在使用 2.x,我将概述一个答案。按照目前的速度,3.x 直到 2019 年才会超过 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'
所以我们需要做的就是模拟raw_input。很简单——Omid Raha's answer to this very question 向我们展示了如何通过我们的模拟实现调出__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')
这里的棘手之处在于您需要在输入with 块之前yield 做出响应。但是在调用 with 块内的 print() 之前,您无法知道该响应是什么。如果字符串是可变的,这会很好,但它们不是。所以取而代之的是一个小的承诺或代理类 - PromiseString。它只做两件事——允许设置一个字符串(或任何东西,真的),并让我们知道它是否等于另一个字符串。 PromiseString 是 yielded,然后设置为 with 块中通常为 print 的值。
希望您能欣赏我写的所有这些诡计,因为我今晚花了大约 90 分钟来整理。我测试了所有这些代码,并验证它们都适用于 Python 2.7。