py3相对import和mock的问题
〇、前言
本文用于记录博主用自己的方法编写mock的时候,与py3相对import的机制发生问题的情况
一、问题描述
情境:
我想测试view_b的代码,但是view_b依赖view_a,为了测试,我要隔离view_a
代码结构:
代码:
view_a.py:
import jiliguala def viewfunca(): print("do view a")
view_b.py:
from . import view_a def viewfuncb(): view_a.viewfunca() print("do view b")
test_core2.py:
# -*- coding: utf-8 -*- import sys import unittest import unittest.mock as mock @mock.patch.dict("sys.modules", { "test.view.view_a": mock.Mock(), }) class TestCore(unittest.TestCase): @mock.patch("test.view.view_a") def test1(self, oMockViewa): def funca(): print("do mock a") from test.view import view_b oMockViewa.side_effect = funca print() view_b.viewfuncb() def test2(self): print(sys.modules)
在pycharm中用unittest运行test1之后出现问题:
File "D:\python 3.6.7\lib\unittest\mock.py", line 1217, in get_original "%s does not have the attribute %r" % (target, name)
AttributeError: <module 'test.view' from 'E:\\game_box\\test\\view\\__init__.py'> does not have the attribute 'view_a'
问题描述:
我隔离了view_a,为什么告诉我初始化view_a的时候出错了?哪里的初始化?
二、原因分析
如果通过删删减减做过几次实验,会发现,如果把test1的装饰器去了,这个就不报错了(代码要改一下)。
那么可以确认问题,问题出在这条语句上。
运行test2:
分析:
1、代码中用于mock的类装饰器的原理是,在系统变量中,加入对应的路径。(从test_core2的test2中可以测试)
2、mock.patch是对环境中的test内的view内的view_a,进行mock
概括一下就是:我虽然在系统变量里加了test.view.view.a,但是我没有【加入test,以及在test里面加入view,在view加入view_a】这个操作。
但是仔细看代码
b.py代码中用了from . import view_a这句话,应该做了之前说的那个初始化操作,为什么没有?
原因:py3中import的执行策略,如果在系统路径中检测到这个路径,直接返回,不会进行初始化操作
这是py代码的优化,但是这个优化,坑了这种mock写法……
验证方法:
这个是python __import__函数的源码:
应该很容易理解,所以这种mock写法,会导致父模块没有被初始化
三、解决方法
1、如果是py2环境,不要用相对导入,直接import
2、在__init__.py中对添加view_a的接口
3、如果不嫌麻烦,不要隔离view_a,可以隔离更具体的接口,比如view_a中的【jiliguala】这种必须隔离的模块(因为不隔离就会报错)
4、换种mock写法,在此不提供了
四、总结
用装饰器写mock,而且直接mock掉想隔离的模块很方便,但是找bug很麻烦。
这里也是感谢某位师兄翻看python源码帮我解决了这个bug(这个不看源码很难找到,因为这种优化不会写文档里面)