没错,您的测试不应验证该random
模块是否在执行其工作;单元测试应该只测试类本身,而不测试它与其他代码的交互方式(应单独测试)。
当然,您的代码完全有可能使用random.randint()
错误的代码。否则您会打电话给random.randrange(1, self._sides)
您,而您的死也永远不会抛出最高价值,但这将是另一种错误,没有一个单元测试可以捕获的错误。在这种情况下,您的die
设备按设计工作,但是设计本身存在缺陷。
在这种情况下,我将使用模拟来替换该randint()
函数,并仅验证它已被正确调用。该unittest.mock
模块附带Python 3.3及更高版本来处理这种类型的测试,但是您可以在旧版本上安装外部mock
软件包以获得完全相同的功能
import unittest
try:
from unittest.mock import patch
except ImportError:
# < python 3.3
from mock import patch
@patch('random.randint', return_value=3)
class TestDice(unittest.TestCase):
def _make_one(self, *args, **kw):
from die import Die
return Die(*args, **kw)
def test_standard_size(self, mocked_randint):
die = self._make_one()
result = die.roll()
mocked_randint.assert_called_with(1, 6)
self.assertEqual(result, 3)
def test_custom_size(self, mocked_randint):
die = self._make_one(sides=42)
result = die.roll()
mocked_randint.assert_called_with(1, 42)
self.assertEqual(result, 3)
if __name__ == '__main__':
unittest.main()
通过模拟,您的测试现在非常简单;真的只有2种情况。6面模具的默认情况和自定义面情况。
还有其他方法可以临时替换的randint()
全局命名空间中的功能Die
,但该mock
模块使此操作最容易。该@mock.patch
装饰这里适用于所有的测试用例的测试方法; 每个测试方法都传递了一个额外的参数,即random.randint()
模拟函数,因此我们可以对模拟进行测试,以查看它是否确实已被正确调用。该return_value
参数指定了在调用模拟时返回的内容,因此我们可以验证该die.roll()
方法确实向我们返回了“随机”结果。
我在这里使用了另一种Python单元测试的最佳实践:将被测类作为测试的一部分导入。该_make_one
方法在测试中完成导入和实例化工作,因此即使您犯了语法错误或其他会阻止原始模块导入的错误,测试模块仍将加载。
这样,如果您在模块代码本身中犯了一个错误,则测试仍将运行;它们只会失败,告诉您代码中的错误。
需要明确的是,以上测试在极端情况下过于简单。例如,此处的目标不是测试random.randint()
使用正确参数调用的对象。相反,目标是测试在给定某些输入的情况下该单元正在产生正确的结果,其中这些输入包括未测试的其他单元的结果。通过模拟该random.randint()
方法,您可以仅控制代码的另一个输入。
在实际测试中,被测单元中的实际代码将变得更加复杂。与传递给API的输入的关系以及随后如何调用其他单元的关系仍然很有趣,而模拟将使您能够访问中间结果,并让您设置这些调用的返回值。
例如,在针对第三方OAuth2服务(多阶段交互)对用户进行身份验证的代码中,您想要测试代码是否将正确的数据传递给该第三方服务,并让您模拟出不同的错误响应,第三方服务将返回,使您可以模拟不同的场景,而不必自己构建完整的OAuth2服务器。在这里,重要的是测试来自第一个响应的信息是否已正确处理并已传递给第二阶段调用,因此您确实希望看到模拟服务已正确调用。