【编者按】mock是一门技术,经过伪造部分实际代码,从而让开发者能够验证剩余代码的正确性。从而,经过mock,开发者能够很是便捷地测试某个函数的内部代码,下面就带你穿梭mock。python
如下为译文函数
本博文主要聚焦mock的使用。mock是一门技术,经过伪造部分实际代码,从而让开发者能够验证剩余代码的正确性。下面将经过几个简单的示例演示mock在Python测试代码中的使用,以及这项极其有用的技术是如何帮助开发者改善测试代码的。单元测试
当进行单元测试时,目标每每是为了测试很是小的代码块,例如一个独立存在的函数或类方法。换句话说,代码测试只针对指定函数的内部代码。若是测试代码须要依赖于其余的代码片断,在某种不幸的情形下,即便被测试的函数没有变化,这部份内嵌代码的修改仍然可能破坏原有的测试。看看下面的例子,你将豁然开朗:测试
# function.py def add_and_multiply(x, y): addition = x + y multiple = multiply(x, y) return (addition, multiple) def multiply(x, y): return x * y # test.py import unittest class MyTestCase(unittest.TestCase): def test_add_and_multiply(self): x = 3 y = 5 addition, multiple = add_and_multiply(x, y) self.assertEqual(8, addition) self.assertEqual(15, multiple) if __name__ == "__main__": unittest.main()
$ python test.py . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
在上面的例子中,`add_and_multiply`计算两个数的和与乘积并返回。“add_and_multiply”调用了另外一个函数“multiply”进行乘积计算。若是指望摒弃“传统“的数学,并从新定义“multiply”函数,在原有的乘积结果上加3。新的“multiply”函数以下:spa
def multiply(x, y): return x * y + 3
如今就会遇到一个问题。即便测试代码没有变化,须要测试的函数也没有变化,然而,“test_add_and_multiply”却会执行失败:code
$ python test.py F ====================================================================== FAIL: test_add_and_multiply (__main__.MyTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 13, in test_add_and_multiply self.assertEqual(15, multiple) AssertionError: 15 != 18 ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1)
这个问题之因此会发生,是由于原始测试代码并不是真正的单元测试。尽管开发者指望测试的是外部函数,但每每隐性地将内部函数也包含进来,因指望结果是依赖于这个内部函数的行为的。虽然在上面这个简单的示例中呈现的差别显得毫无心义,但某些场景下,咱们须要测试一个复杂的逻辑代码块,例如,一个Django视图函数基于某些特定条件调用各类不一样的内部功能,从函数调用结果中分离出视图逻辑的测试就显得尤其重要了。对象
解决这个问题有两种方案。要么忽略,像集成测试那样去进行单元测试,要么求助于mock。第一种方案的缺点是,集成测试仅仅告诉咱们函数调用时哪一行代码出问题了,这样很难找到问题根源所在。这并非说,集成测试没有用处,由于在某些状况下它确实很是有用。无论怎样,单元测试和集成测试用于解决不一样的问题,它们应该被同时使用。所以,若是想要成为一个好的测试人员,mock是一个不错的替代选择。ip
mock是一个极其优秀的Python包,Python 3已将其归入标准库。对于咱们这些还在UnicodeError遍及的Python 2.x中挣扎的苦逼码农,能够经过pip进行安装:开发
pip install mock==1.0.1
mock有多种不一样的用法。咱们能够用它提供猴子补丁功能,建立伪造的对象,甚至能够做为一个上下文管理器。全部这些都是基于一个共同目标的,用副本替换部分代码来收集信息并返回伪造的响应。文档
mock的[ 文档]很是密集,寻找特定的用例信息可能会很是棘手。这里,咱们就来看看一个常见的场景——替换一个内嵌函数并检查它的输入和输出。
下面将使用mock从新编写单元测试。接下来,会讨论发生了什么,以及为何从测试的角度来看它是很是有用的:
# test.py import mock import unittest class MyTestCase(unittest.TestCase): @mock.patch('multiply') def test_add_and_multiply(mock_multiply): x = 3 y = 5 mock_multiply.return_value = 15 addition, multiple = add_and_multiply(x, y) mock_multiply.assert_called_once_with(3, 5) self.assertEqual(8, addition) self.assertEqual(15, multiple) if __name__ == "__main__": unittest.main()
至此,咱们能够改变“multiply”函数来作任何事情——它可能返回加3后的乘积,返回None,或返回favourite line from Monty Python and the Holy Grail——你会发现,咱们上面的测试仍然能够经过。这是由于咱们mock了“multiply”函数。在真正的单元测试场景下,咱们并不关心“multiply”函数内部发生了什么,从测试“add_and_multiply”的角度来看,只关心“multiply”被正确的参数调用了。这里咱们假定有另外一个单元测试会针对“multiply”的内部逻辑进行测试。
乍一看,上面的语法可能很差理解。下面逐行分析:
@mock.patch('multiply') def test_add_and_multiply(mock_multiply):
使用“mock.patch”装饰器来用mock对象替换"multiply'。而后将它做为一个参数"mock_multiply"插入到测试代码中。在这个测试的上下文中,任何对“multiply”的调用都会被重定向到“mock_multiply”对象。
有人会质疑——“怎么能用对象替换函数!”别担忧!在Python的世界,函数也是对象。一般状况下,当咱们调用“multiply()”,实际执行的是“multiply”函数的“__call__”方法。然而,恰当的使用mock,对“multiply()”的调用将执行mock对象,而不是“__call__”方法。
mock_multiply.return_value = 15
为了使mock函数能够返回任何东西,咱们须要定义其“return_value”属性。实际上,当mock函数被调用时,它用于定义mock对象的返回值。
addition, multiple = add_and_multiply(x, y) mock_multiply.assert_called_once_with(3, 5)
在测试代码中,咱们调用了外部函数“add_and_multiply”。它会调用内嵌的`multiply`函数,若是咱们正确的进行了mock,调用将会被定义的mock对象取代。为了验证这一点,咱们能够用到mock对象的高级特性——当它们被调用时,传给它们的任何参数将被储存起来。顾名思义,mock对象的“assert_called_once_with”方法就是一个不错的捷径来验证某个对象是否被一组特定的参数调用过。若是被调用了,测试经过。反之,“assert_called_once_with”会抛出`AssertionError`的异常。
这里会遇到不少实际问题。首先,咱们经过mock将“multiply”函数从“add_and_multiply”中分离出来。这就意味着咱们的单元测试只针对“add_and_multiply”的内部逻辑。只有针对“add_and_multiply”的代码修改将影响测试的成功与否。
其次,能够控制内嵌函数的输出,以确保外部函数处理了不一样的状况。例如,“add_and_multiply”可能有逻辑条件依赖于“multiply”的返回值:好比说,咱们只想在乘积大于10的条件下返回一个值。经过人为设定“multiply”的返回值,咱们能够模拟乘积小于10的状况以及乘积大于10的状况,从而能够很容易测试咱们的逻辑正确性。
最后,咱们如今能够验证被mock的函数被调用的次数,并传入了正确的参数。因为咱们的mock对象取代了“multiply”函数的位置,咱们知道任何针对“multiply”函数的调用都会被重定向到该mock对象。当测试一个复杂的功能时,确保每一步都被正确调用将是一件很是使人欣慰的事情。