我正试着处理单元测试。
假设我们有一个模具,可以有一个默认的边数等于6(但可以是4,5边等):
import random
class Die():
def __init__(self, sides=6):
self._sides = sides
def roll(self):
return random.randint(1, self._sides)以下是否是有效/有用的单元测试?
我只是认为这些都是浪费时间,因为随机模块已经存在了足够长的时间,但是我认为如果随机模块得到更新(假设我更新了Python版本),那么至少我已经被覆盖了。
此外,我是否还需要测试模具辊的其他变化,例如,在这种情况下的3,或者它是好的涵盖另一个初始化的模具状态?
发布于 2014-01-26 09:55:22
您是对的,您的测试不应该验证random模块是否完成了它的工作;单元测试应该只测试类本身,而不是它与其他代码的交互方式(这些代码应该单独测试)。
当然,您的代码完全有可能错误地使用了random.randint();或者您正在调用random.randrange(1, self._sides),而您的死代码永远不会抛出最高的值,但这将是一种不同类型的错误,而不是您可以用单一测试捕获的错误。在这种情况下,您的die单元正在按设计工作,但设计本身存在缺陷。
在本例中,我将使用function替换randint()函数,并且只验证它是否已被正确调用。Python3.3及更高版本附带了unittest.mock模块来处理这种类型的测试,但是您可以在旧版本上安装外部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()使用模拟,您的测试现在非常简单;只有两个案例,真的。6边模的默认情况,以及自定义边的情况.
还有其他方法可以暂时替换randint()函数在Die的全局命名空间中,但是mock模块使这一点变得更简单。这里的@mock.patch装潢工适用于测试用例中的所有测试方法;每个测试方法都被传递了一个额外的参数,即模拟的random.randint()函数,因此我们可以对模拟测试来查看它是否确实被正确调用。return_value参数指定调用模拟时返回的内容,因此我们可以验证die.roll()方法确实将‘随机’结果返回给我们。
我在这里使用了另一个Python单元测试最佳实践:导入测试中的类作为测试的一部分。_make_one方法在测试中执行导入和实例化工作,因此即使您犯了语法错误或其他会阻止原始模块导入的错误,测试模块仍然会加载。
这样,如果您在模块代码本身中犯了错误,测试仍将运行;它们只会失败,告诉您代码中的错误。
要明确的是,上面的测试是极端的简单化。这里的目标不是测试是否使用正确的参数调用了random.randint(),例如。相反,目标是测试给定某些输入的单元是否产生正确的结果,其中这些输入包括未被测试的其他单元的结果。通过模拟random.randint()方法,您可以控制代码的另一个输入。
在实际测试中,测试单元中的实际代码将更加复杂;与传递给API的输入之间的关系以及随后如何调用其他单元仍然是很有趣的,而模拟将使您访问中间结果,并允许您为这些调用设置返回值。
例如,在针对第三方OAuth2服务(多阶段交互)对用户进行身份验证的代码中,您希望测试您的代码是否将正确的数据传递给该第三方服务,并允许您模拟出第三方服务将返回的不同错误响应,从而可以模拟不同的场景,而不必自己构建完整的OAuth2服务器。在这里,重要的是测试来自第一个响应的信息是否已被正确处理并传递到第二阶段调用,因此您确实希望看到模拟的服务被正确调用。
发布于 2014-03-05 23:17:19
固定随机种子。对于1,2,5和12边的骰子,确认几千个卷给出的结果包括1和N,而不包括0或N+1。如果看起来很奇怪,你会得到一组不包括预期范围的随机结果,切换到另一个种子。
嘲笑工具是很酷的,但是仅仅因为它们允许你做一件事并不意味着应该去做。YAGNI不仅适用于特性,也适用于测试夹具。
如果您可以很容易地使用非模拟的依赖项进行测试,那么您几乎总是应该这样做;这样,您的测试就会专注于减少缺陷计数,而不仅仅是增加测试数量。过度的嘲弄可能会产生误导的覆盖率数字,这反过来会导致将实际测试推迟到以后的某个阶段
发布于 2014-03-24 14:37:22
如果你想一想,什么是Die?-不过是一个包装random的包装器。它封装random.randint并根据应用程序自己的词汇表重新命名它:Die.Roll。
我不认为在Die和random之间插入另一个抽象层是相关的,因为Die本身已经是应用程序和平台之间的间接层。
如果您想要罐头骰子的结果,只需模拟Die__,不要嘲笑random。
通常,我不对与外部系统通信的包装器对象进行单元测试,而是为它们编写集成测试。您可以为Die编写一些这样的代码,但是正如您所指出的,由于底层对象的随机性,它们将没有意义。此外,这里不涉及配置或网络通信,因此除了平台调用之外,没有什么可测试的。
=>考虑到Die只是几行琐碎的代码,与random本身相比几乎没有逻辑,我将跳过在这个特定示例中对它进行测试。
https://softwareengineering.stackexchange.com/questions/225523
复制相似问题