Python、单元测试和模拟导入

问题描述 投票:0回答:6

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

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

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

编辑

谢谢大家的回答。

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

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

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

python unit-testing refactoring python-import
6个回答
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', [])

mock_import

的第二个参数是您
do想要在内部模块中导入的模块名称列表。

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


1
投票
如果你真的想研究一下 python 导入机制,请查看

ihooks

 模块。它提供了用于更改 __import__
 内置行为的工具。但从你的问题中并不清楚为什么你需要这样做。


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. 模拟它进行独立测试。
您会测试 Python 库吗?如果是这样,你就有很多工作要做。如果没有,那么,您也许应该只模拟您实际要测试的东西。


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()
    


0
投票

相关问题

从 app.services 导入搜索

search() 位于 search_service.py 内部 其中有 10 个来自我的本地代码库的导入,其中之一是秘密管理器,它在启动时自动初始化。因此,当我导入搜索时,它会尝试连接到机密管理器。

有没有办法只导入搜索方法来测试它,而不需要 python 尝试导入 search_service.py 中的所有导入。

TIA。

© www.soinside.com 2019 - 2024. All rights reserved.