避免在单元测试中运行顶层模块代码

Pau*_*ldo 7 python unit-testing monkeypatching

我正在尝试对一些导入模块的 Python 3 代码进行单元测试。不幸的是,模块的编写方式,简单地导入它会产生令人不快的副作用,这对测试来说并不重要。我试图用unitest.mock.patch它来解决它,但运气不佳。

这是说明性示例的结构:

.
??? work
    ??? __init__.py
    ??? test_work.py
    ??? work.py
    ??? work_caller.py
Run Code Online (Sandbox Code Playgroud)

__init__.py 是一个空文件

工作.py

.
??? work
    ??? __init__.py
    ??? test_work.py
    ??? work.py
    ??? work_caller.py
Run Code Online (Sandbox Code Playgroud)

work_caller.py

import os


def work_on():
    path = os.getcwd()
    print(f"Working on {path}")
    return path

def unpleasant_side_effect():
    print("I am an unpleasant side effect of importing this module")

# Note that this is called simply by importing this file
unpleasant_side_effect()
Run Code Online (Sandbox Code Playgroud)

测试工作.py

from work.work import work_on

class WorkCaller:
    def call_work(self):
        # Do important stuff that I want to test here

        # This call I don't care about in the test, but it needs to be called
        work_on()
Run Code Online (Sandbox Code Playgroud)

work_caller.py我只想测试开始代码,而不是调用work_on(). 当我运行测试时,我得到以下输出:

paul-> python -m unittest
I am an unpleasant side effect of importing this module
Working on /Users/paul/src/patch-test
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
Run Code Online (Sandbox Code Playgroud)

我原以为I am an unpleasant side effect of importing this module不会打印该行,因为该函数unpleasant_side_effect会被模拟。我可能哪里出错了?

小智 5

unpleasant_side_effect运行有两个原因。首先,因为导入是在测试用例开始之前处理的,因此在导入发生时不会被模拟。其次,因为模拟本身会导入work.py并因此unpleasant_side_effect即使work_caller.py未导入也能运行。

导入问题可以通过模拟模块work.py本身来解决。这既可以在测试模块中全局完成,也可以在测试用例本身中完成。这里我给它分配了一个MagicMock,它可以被导入,被调用等等。

测试工作.py

from unittest import TestCase, mock


class TestWorkMockingModule(TestCase):
    def test_workcaller(self):
        import sys
        sys.modules['work.work'] = mock.MagicMock()
        from work.work_caller import WorkCaller

        sut = WorkCaller()
        sut.call_work()
Run Code Online (Sandbox Code Playgroud)

缺点是 work_on 也被嘲笑,我不确定你的情况是否有问题。

导入时不可能不运行整个模块,因为函数和类也是语句,因此模块执行必须在返回调用者之前完成,调用者想要更改导入的模块。