Python、单元测试和模拟导入

发布于 2024-07-06 14:51:36 字数 436 浏览 9 评论 0原文

我正在参与一个项目,我们正在开始重构一些庞大的代码库。 立即出现的一个问题是每个文件都会导入许多其他文件。 我如何以一种优雅的方式在单元测试中模拟它,而不必更改实际代码,以便我可以开始编写单元测试?

举个例子:具有我想要测试的功能的文件导入了十个其他文件,这些文件是我们软件的一部分,而不是 python 核心库。

我希望能够尽可能单独地运行单元测试,现在我只想测试不依赖于正在导入的文件中的内容的函数。

编辑

感谢您的所有答案。

从一开始我并不真正知道自己想做什么,但现在我想我知道了。

问题是,由于某些第三方自动魔法,某些导入仅在整个应用程序运行时才可能进行。 因此,我必须在我用 sys.path 指出的目录中为这些模块创建一些存根。

现在我可以导入包含我想要在单元测试文件中编写测试的函数的文件,而不会抱怨缺少模块。

I am in a project where we are starting refactoring some massive code base. One problem that immediately sprang up is that each file imports a lot of other files. How do I in an elegant way mock this in my unit test without having to alter the actual code so I can start to write unit-tests?

As an example: The file with the functions I want to test, imports ten other files which is part of our software and not python core libs.

I want to be able to run the unit tests as separately as possible and for now I am only going to test functions that does not depend on things from the files that are being imported.

EDIT

Thanks for all the answers.

I didn't really know what I wanted to do from the start but now I think I know.

Problem was that some imports was only possible when the whole application was running because of some third-party auto-magic. So I had to make some stubs for these modules in a directory which I pointed out with sys.path

Now I can import the file which contains the functions I want to write tests for in my unit-test file without complaints about missing modules.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

呆萌少年 2024-07-13 14:51:36

如果您想导入一个模块,同时确保它不导入任何内容,您可以替换 __import__ 内置函数。

例如,使用此类:

class ImportWrapper(object):
    def __init__(self, real_import):
        self.real_import = real_import

    def wrapper(self, wantedModules):
        def inner(moduleName, *args, **kwargs):
            if moduleName in wantedModules:
                print "IMPORTING MODULE", moduleName
                self.real_import(*args, **kwargs)
            else:
                print "NOT IMPORTING MODULE", moduleName
        return inner

    def mock_import(self, moduleName, wantedModules):
        __builtins__.__import__ = self.wrapper(wantedModules)
        try:
            __import__(moduleName, globals(), locals(), [], -1)
        finally:
            __builtins__.__import__ = self.real_import

在您的测试代码中,不要编写 import myModule,而是编写:

wrapper = ImportWrapper(__import__)
wrapper.mock_import('myModule', [])

mock_import 的第二个参数是您的模块名称列表确实想要导入内部模块。

这个示例可以进一步修改,例如导入所需的其他模块,而不是仅仅不导入它,甚至用您自己的一些自定义对象来模拟模块对象。

If you want to import a module while at the same time ensuring that it doesn't import anything, you can replace the __import__ builtin function.

For example, use this class:

class ImportWrapper(object):
    def __init__(self, real_import):
        self.real_import = real_import

    def wrapper(self, wantedModules):
        def inner(moduleName, *args, **kwargs):
            if moduleName in wantedModules:
                print "IMPORTING MODULE", moduleName
                self.real_import(*args, **kwargs)
            else:
                print "NOT IMPORTING MODULE", moduleName
        return inner

    def mock_import(self, moduleName, wantedModules):
        __builtins__.__import__ = self.wrapper(wantedModules)
        try:
            __import__(moduleName, globals(), locals(), [], -1)
        finally:
            __builtins__.__import__ = self.real_import

And in your test code, instead of writing import myModule, write:

wrapper = ImportWrapper(__import__)
wrapper.mock_import('myModule', [])

The second argument to mock_import is a list of module names you do want to import in inner module.

This example can be modified further to e.g. import other module than desired instead of just not importing it, or even mocking the module object with some custom object of your own.

累赘 2024-07-13 14:51:36

如果你真的想研究一下 python 导入机制,请查看 ihooks 模块。 它提供了用于更改 __import__ 内置行为的工具。 但从你的问题中并不清楚为什么你需要这样做。

If you really want to muck around with the python import mechanism, take a look at the ihooks module. It provides tools for changing the behavior of the __import__ built-in. But it's not clear from your question why you need to do this.

寒尘 2024-07-13 14:51:36

“导入很多其他文件”? 导入属于您的自定义代码库一部分的许多其他文件? 或者导入 Python 发行版中的许多其他文件? 或者导入很多其他开源项目文件?

如果您的导入不起作用,则说明您遇到了一个“简单”的 PYTHONPATH 问题。 将所有各种项目目录放入可用于测试的 PYTHONPATH 中。 我们有一个相当复杂的路径,在 Windows 中,我们像这样管理它。

@set Part1=c:\blah\blah\blah
@set Part2=c:\some\other\path
@set that=g:\shared\stuff
set PYTHONPATH=%part1%;%part2%;%that%

我们将路径的每个部分分开,以便我们(a)知道事物来自哪里,并且(b)可以在我们移动事物时管理变化。

由于 PYTHONPATH 是按顺序搜索的,因此我们可以通过调整路径上的顺序来控制使用的内容。

一旦你拥有了“一切”,这就变成了信任问题。

要么

  • 您信任某些东西(即Python 代码库)并导入它。

或者

  • 您不信任某些东西(即您自己的代码)并且您

    1. 单独测试
    2. 模拟它以进行独立测试。

你会测试Python 库吗? 如果是这样,你就有很多工作要做。 如果没有,那么,您也许应该只模拟您实际要测试的东西。

"imports a lot of other files"? Imports a lot of other files that are part of your customized code base? Or imports a lot of other files that are part of the Python distribution? Or imports a lot of other open source project files?

If your imports don't work, you have a "simple" PYTHONPATH problem. Get all of your various project directories onto a PYTHONPATH that you can use for testing. We have a rather complex path, in Windows we manage it like this

@set Part1=c:\blah\blah\blah
@set Part2=c:\some\other\path
@set that=g:\shared\stuff
set PYTHONPATH=%part1%;%part2%;%that%

We keep each piece of the path separate so that we (a) know where things come from and (b) can manage change when we move things around.

Since the PYTHONPATH is searched in order, we can control what gets used by adjusting the order on the path.

Once you have "everything", it becomes a question of trust.

Either

  • you trust something (i.e., the Python code base) and just import it.

Or

  • You don't trust something (i.e., your own code) and you

    1. test it separately and
    2. mock it for stand-alone testing.

Would you test the Python libraries? If so, you've got a lot of work. If not, then, you should perhaps only mock out the things you're actually going to test.

花开浅夏 2024-07-13 14:51:36

如果您想在单元测试之前快速修复,则无需进行困难的操作。

如果单元测试与您要测试的代码位于同一文件中,只需从 globals() 字典中删除不需要的模块即可。

这是一个相当冗长的示例:假设您有一个模块 impp.py ,其内容为:

value = 5

现在,在您的测试文件中您可以编写:

>>> import impp
>>> print globals().keys()
>>> def printVal():
>>>     print impp.value
['printVal', '__builtins__', '__file__', 'impp', '__name__', '__doc__']

请注意,impp 属于全局变量,因为它是进口的。 调用使用 impp 模块的函数 printVal 仍然有效:

>>> printVal()
5

但是现在,如果您从 globals() 中删除 impp 键>...

>>> del globals()['impp']
>>> print globals().keys()
['printVal', '__builtins__', '__file__', '__name__', '__doc__']

...并尝试调用 printVal(),您将得到:

>>> printVal()
Traceback (most recent call last):
  File "test_imp.py", line 13, in <module>
    printVal()
  File "test_imp.py", line 5, in printVal
    print impp.value
NameError: global name 'impp' is not defined

...这可能正是您想要实现的目标。

要在单元测试中使用它,您可以在运行测试套件之前删除全局变量,例如在 __main__ 中:

if __name__ == '__main__':
    del globals()['impp']
    unittest.main()

No difficult manipulation is necessary if you want a quick-and-dirty fix before your unit-tests.

If the unit tests are in the same file as the code you wish to test, simply delete unwanted module from the globals() dictionary.

Here is a rather lengthy example: suppose you have a module impp.py with contents:

value = 5

Now, in your test file you can write:

>>> import impp
>>> print globals().keys()
>>> def printVal():
>>>     print impp.value
['printVal', '__builtins__', '__file__', 'impp', '__name__', '__doc__']

Note that impp is among the globals, because it was imported. Calling the function printVal that uses impp module still works:

>>> printVal()
5

But now, if you remove impp key from globals()...

>>> del globals()['impp']
>>> print globals().keys()
['printVal', '__builtins__', '__file__', '__name__', '__doc__']

...and try to call printVal(), you'll get:

>>> printVal()
Traceback (most recent call last):
  File "test_imp.py", line 13, in <module>
    printVal()
  File "test_imp.py", line 5, in printVal
    print impp.value
NameError: global name 'impp' is not defined

...which is probably exactly what you're trying to achieve.

To use it in your unit-tests, you can delete the globals just before running the test suite, e.g. in __main__:

if __name__ == '__main__':
    del globals()['impp']
    unittest.main()
花间憩 2024-07-13 14:51:36

在您的评论上面中,您说您想让python确信某些模块已经导入。 这看起来仍然是一个奇怪的目标,但如果这确实是您想要做的,原则上您可以在导入机制的背后偷偷摸摸,并更改sys.modules。 不确定这对于包导入如何工作,但对于绝对导入应该没问题。

In your comment above, you say you want to convince python that certain modules have already been imported. This still seems like a strange goal, but if that's really what you want to do, in principle you can sneak around behind the import mechanism's back, and change sys.modules. Not sure how this'd work for package imports, but should be fine for absolute imports.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文