14

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

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

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

感谢所有的答案。

从一开始我真的不知道我想做什么,但现在我想我知道了。

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

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

4

5 回答 5

8

如果你想导入一个模块,同时确保它不导入任何东西,你可以替换__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', [])

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

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

于 2008-10-07T17:30:27.597 回答
1

“导入很多其他文件”?导入许多属于您的自定义代码库的其他文件?或者导入许多属于 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 库吗?如果是这样,你有很多工作。如果不是,那么,你或许应该只模拟你实际要测试的东西。

于 2008-10-07T13:59:18.833 回答
1

如果你真的想搞砸 python 导入机制,看看这个ihooks模块。它提供了用于更改__import__内置行为的工具。但是从您的问题中不清楚为什么需要这样做。

于 2008-10-07T14:05:11.660 回答
0

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

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

这是一个相当长的例子:假设你有一个impp.py包含内容的模块:

value = 5

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

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

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

>>> printVal()
5

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

>>> 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()
于 2008-10-07T14:53:38.130 回答
0

在你上面的评论,你说你想说服 python 某些模块已经被导入。这似乎仍然是一个奇怪的目标,但如果这真的是你想做的,原则上你可以在导入机制的背后偷偷摸摸,改变sys.modules. 不确定这对于包导入如何工作,但对于绝对导入应该没问题。

于 2008-10-07T15:30:34.720 回答