Mock在单元测试中随处可见。在Python的单元测试中,常见的有unittest和pytest。unittest对应的Mock库是unittest.mock,pytest对应的是pytest-mock。这两个Mock库中都存在Patchers,Patchers与Mock密不可分
什么是Patch
Pytest-mock本质上与unittest.mock是相同的API,因此这里我们先从unittest.mock说起
A simple example
图1. Example code
当测试目标是BubbleTeaShop
的make_milk_tea
方法,但又不想实际调用Kitchen
的make_milk_tea
方法时,就可以使用下图中的@patch()
方法对class进行patch,并使用instance.make_milk_tea.return_value
指定了方法的返回值
图2. 使用patch进行mock
上例bubbleteashop.py
中的Kitchen
被@patch
所替换,@patch
所返回的是一个unittst.mock
中的MagicMock
对象
图3. 被MagicMock替换
Where to Patch
在官方文档中讲述Patch使用之前提到
Note: The key is to do the patching in the right namespace. See the section where to patch.
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.
即在object被查找(被使用)的地方进行patch,而非定义的地方。并且文档也提到一个前提:object是通过from import
导入的
以上例来讲,我们通过from src.kitchen import Kitchen
的方式导入Kitchen
,
所以patch的是src.bubbleteashop.Kitchen
,而非src.kictchen.Kitchen
原因都与python的赋值机制直接相关
图4. 引用[Ned Batchelder - Python Names and Values](https://nedbatchelder.com/text/names1.html)
Ned Batchelder在Pycon 2015中分享时提到,Python 中的变量是引用值的名称。如果分配第二个名称,这些名称不会相互引用,它们都引用相同的值(x与y都指向了23)。如果然后再次分配其中一个名称(x=12),则另一个名称不受影响(y仍然是23)
在之前的例子中,from src.kitchen import Kitchen
就等同于先导入src.kitchen
,再进行赋值kitchen = src.kitchen.Kitchen
,kitchen如同y,src.kitchen.Kitchen如同x。
如果以src.bubbleteashop.Kitchen
进行patch时,bubbleteashop.py
的Kitchen
会被替换为MagicMock,这正是测试中所希望达到的目标
如果以src.kictchen.Kitchen
进行patch时,kitchen.py
的Kitchen
会被替换为MagicMock,而bubbleteashop.py
的Kitchen
不受影响,并没有被mock
图5. “from import”导入
如果我们以import src.kitchen
进行导入呢?
图6. “import src.kitchen” 示例
import src.kitchen
等同于在bubbleteashop.py
中使src.kitchen
指代整个kitchen模块,访问src.kitchen.make_milk_tea
会进入到kitchen中,找到make_milk_tea
,并执行它。
如果以src.kictchen.Kitchen
进行patch时,kitchen.py
的Kitchen
会被替换为MagicMock,因为bubbleteashop.py
的src.kitchen
指代整个kitchen模块,所以mock同样会影响到bubbleteashop.py
图7. “import”导入
Patch scope
Patch主要有三种方式,每一种方式的scope不同,使用场景也不同
图8. Patch scope
Context manager
使用with的声明进行patch,patch的范围仅限于with语句的语句体
图9. Context manager scope
Function decorator
如同上例中创建在测试方法上的@patch(‘src.bubbleteashop.Kitchen’)
就是一个function decorator,仅作用于当前测试方法
图10. Function decorator scope
Class decorator
同样采用@pathch()
的方法进行创建,不过是创建在class上,作用于这个test class的每一个方法,适用于多个测试方法共享同样的patcher
图11. Class decorator scope