2008-10-07 125 views
12

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

作为示例:具有我要测试的函数的文件会导入十个其他文件,这些文件是我们的软件的一部分,而不是python核心库。

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

感谢您的所有答案。

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

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

现在我可以在我的单元测试文件中导入包含我想写测试的函数的文件,缺少模块。

回答

7

如果您想在导入模块的同时确保其不导入任何内容,则可以替换__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是模块的名字,你的列表想要在内部模块中导入。

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

+0

您希望在mock_import方法中添加`try:finally:`,以避免在出错时使用包装导入而不是默认导入。 – Yonatan 2013-02-19 08:37:02

1

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

如果您的进口不起作用,您有一个“简单”PYTHONPAT H问题。把你所有的各种项目目录放到你可以用来测试的PYTHONPATH上。我们有一个相当复杂的路径,在Windows中,我们管理它这样

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

我们保持每件路径的独立,使我们(一)知道在哪里,都是从和(b)当我们移动可以管理变革周围的事物。

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

一旦你拥有了“一切”,它就成为一个信任的问题。

要么

  • 你相信的东西(即Python代码库)和刚刚导入。

或者

  • 你不相信的东西(也就是你自己的代码),你

    1. 测试单独和
    2. 嘲笑它的独立测试。

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

1

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

0

如果你想在你的单元测试之前做一个快速修复的修复,那么不需要困难的操作。

如果单元测试与您要测试的代码位于同一文件中,只需从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() 
+0

感谢您的回答我从中学到了很多东西。但事实上,我想要做的是相反的事情。测试是在另一个文件中,当我在这个导入包含我想测试的函数的文件时,我希望该文件认为所有其他文件已经被导入。 – 2008-10-07 14:59:57

0

在您的评论above,你说你要说服蟒蛇某些模块已经导入。这仍然看起来像一个奇怪的目标,但如果这真的是你想要做的,原则上你可以潜入进口机制背后,并改变sys.modules。不知道这是如何工作的包进口,但绝对进口应罚款。