简体   繁体   English

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

[英]Avoiding running top-level module code in unit test

I am attempting to unit test some Python 3 code that imports a module.我正在尝试对一些导入模块的 Python 3 代码进行单元测试。 Unfortunately, the way the module is written, simply importing it has unpleasant side effects, which are not important for the tests.不幸的是,模块的编写方式,简单地导入它会产生令人不快的副作用,这对测试并不重要。 I'm trying to use unitest.mock.patch to get around it, but not having much luck.我正在尝试使用unitest.mock.patch来解决它,但运气不佳。

Here is the structure of an illustrative sample:这是一个说明性示例的结构:

.
└── work
    ├── __init__.py
    ├── test_work.py
    ├── work.py
    └── work_caller.py

__init__.py is an empty file __init__.py是一个空文件

work.py工作.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()

work_caller.py work_caller.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()

test_work.py测试工作.py

from unittest import TestCase, mock

from work.work_caller import WorkCaller


class TestWorkMockingModule(TestCase):
    def test_workcaller(self):
        with mock.patch("work.work.unpleasant_side_effect") as mocked_function:
            sut = WorkCaller()
            sut.call_work()

In work_caller.py I only want to test the beginning code, not the call to work_on() .work_caller.py ,我只想测试开始代码,而不是对work_on()的调用。 When I run the test, I get the following output:当我运行测试时,我得到以下 output:

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

I was expecting that the line I am an unpleasant side effect of importing this module would not be printed because the function unpleasant_side_effect would be mocked.我原以为I am an unpleasant side effect of importing this module行不会被打印,因为 function unpleasant_side_effect会被嘲笑。 Where might I be going wrong?我可能哪里出错了?

The unpleasant_side_effect is run for two reasons. unpleasant_side_effect的运行有两个原因。 First because the imports are handled before the test case is started and is therefore not mocked when importing is happening.首先,因为导入是在测试用例开始之前处理的,因此在导入时不会被模拟。 Secondly, because the mocking itself imports work.py and thus runs unpleasant_side_effect even if work_caller.py was not imported.其次,因为 mocking 本身导入了work.py ,因此即使没有导入work_caller.py也会运行unpleasant_side_effect

The import problem can be solved by mocking the module work.py itself.导入问题可以通过 mocking 模块work.py本身来解决。 This can either be done globally in the test module or in the testcase itself.这可以在测试模块或测试用例本身中全局完成。 Here I assigned it a MagicMock , which can be imported, called etc.在这里,我为它分配了一个MagicMock ,可以导入、调用等。

test_work.py测试工作.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()

The downside is that work_on is also mocked, which I am not sure whether is a problem in your case.缺点是 work_on 也被嘲笑,我不确定你的情况是否有问题。

It is not possible to not run the entire module when it is imported, since functions and classes are also statements, thus the module execution has to finish before returning to the caller, where one want to alter the imported module.导入时不可能不运行整个模块,因为函数和类也是语句,因此模块执行必须在返回到调用者之前完成,调用者想要更改导入的模块。

In case you asked partially about the best practice.如果您部分询问最佳实践。

You should always split your code to library used by every other code and side-effect lines.您应该始终将代码拆分为所有其他代码和副作用行使用的库。 And probably eliminate side-effects by calling the side-effecting code from you def main(): But if you want to keep side-effects anyway, then you could do:并且可能通过调用def main():但如果你想保留副作用,那么你可以这样做:

work_lib.py : work_lib.py

...no side-effects...

work.py

from work_lib import ...

...side-effects....

test_work.py

from work_lib import ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM