简体   繁体   English

Python,单元测试和模拟导入

[英]Python, unit-testing and mocking imports

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. 举个例子:带有我要测试的函数的文件,导入其他十个文件,这些文件是我们软件的一部分而不是python核心库。

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. 我希望能够尽可能单独运行单元测试,现在我只测试不依赖于正在导入的文件中的内容的函数。

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 所以我不得不在我用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. 现在我可以在我的单元测试文件中导入包含我想要编写测试功能的文件,而不会抱怨缺少模块。

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. 如果要在导入模块的同时确保它不导入任何内容,可以替换__import__内置函数。

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: 在您的测试代码中,不要编写import myModule ,而是写:

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. 的第二个参数mock_import是模块名称的列表, 想内部模块中导入。

This example can be modified further to eg import other module than desired instead of just not importing it, or even mocking the module object with some custom object of your own. 此示例可以进一步修改为例如导入除期望之外的其他模块,而不是仅仅导入它,甚至可以使用您自己的某个自定义对象模拟模块对象。

"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? 或者导入许多属于Python发行版的其他文件? Or imports a lot of other open source project files? 还是导入了很多其他开源项目文件?

If your imports don't work, you have a "simple" PYTHONPAT H problem. 如果您的导入不起作用,则会出现“简单”的PYTHONPAT H问题。 Get all of your various project directories onto a PYTHONPATH that you can use for testing. 将所有各种项目目录放到可用于测试的PYTHONPATH上。 We have a rather complex path, in Windows we manage it like this 我们有一个相当复杂的路径,在Windows中我们像这样管理它

@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. 我们将每条路径分开,以便我们(a)知道事物的来源和(b)在我们移动物体时可以管理变化。

Since the PYTHONPATH is searched in order, we can control what gets used by adjusting the order on the path. 由于按顺序搜索PYTHONPATH ,我们可以通过调整路径上的顺序来控制使用的内容。

Once you have "everything", it becomes a question of trust. 一旦你拥有了“一切”,它就成了一个信任的问题。

Either

  • you trust something (ie, the Python code base) and just import it. 你相信某些东西(即Python代码库)并只是导入它。

Or 要么

  • You don't trust something (ie, your own code) and you 你不相信某些东西(即你自己的代码)和你

    1. test it separately and 单独测试它
    2. mock it for stand-alone testing. 嘲笑它进行独立测试。

Would you test the Python libraries? 你会测试Python库吗? 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. 如果没有,那么,你应该只模拟你实际要测试的东西。

If you really want to muck around with the python import mechanism, take a look at the ihooks module. 如果你真的想要使用python导入机制,请查看ihooks模块。 It provides tools for changing the behavior of the __import__ built-in. 它提供了用于更改__import__内置行为的工具。 But it's not clear from your question why you need to do this. 但是你的问题并不清楚为什么你需要这样做。

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

Here is a rather lengthy example: suppose you have a module impp.py with contents: 这是一个相当冗长的例子:假设你有一个包含内容的模块impp.py

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. 请注意, impp属于全局变量,因为它是导入的。 Calling the function printVal that uses impp module still works: 调用使用impp模块的函数printVal仍然有效:

>>> printVal()
5

But now, if you remove impp key from globals() ... 但现在,如果你从globals()删除impp键...

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

...and try to call printVal() , you'll get: ...并尝试调用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

...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, eg in __main__ : 要在单元测试中使用它,您可以在运行测试套件之前删除全局变量,例如在__main__

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

In your comment above , you say you want to convince python that certain modules have already been imported. 上面的评论中,你说你想说服python已经导入了某些模块。 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 . 这似乎仍然是一个奇怪的目标,但如果这真的是你想要做的,原则上你可以在导入机制的后面偷偷摸摸,并改变sys.modules Not sure how this'd work for package imports, but should be fine for absolute imports. 不确定这对于包导入是如何工作的,但绝对导入应该没问题。

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

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