2011-05-01 64 views
43

说我有以下模块:如何测试或模拟“如果__name__ ==‘__main__’”内容

def main(): 
    pass 

if __name__ == "__main__": 
    main() 

我想写的下半部分单元测试(我想实现100%的覆盖率)。我发现执行import/__name__设置机制的内置模块,但我无法弄清楚如何模拟或以其他方式检查是否调用了函数。

这是我到目前为止已经试过:

import runpy 
import mock 

@mock.patch('foobar.main') 
def test_main(self, main): 
    runpy.run_module('foobar', run_name='__main__') 
    main.assert_called_once_with() 

回答

34

我将选择另一个替代方案,即从覆盖率报告中排除if __name__ == '__main__',当然,只有在测试中已经有main()函数的测试用例时,才可以这样做。

至于为什么我选择排除而不是为整个脚本编写一个新的测试用例是因为如果我说你已经有了main()函数的测试用例,那么你添加了另一个测试用例该脚本(仅用于100%覆盖)将只是一个重复的脚本。

对于如何排除if __name__ == '__main__'你可以写一个覆盖配置文件,并添加部分报告:关于覆盖配置文件

[report] 

exclude_lines = 
    if __name__ == .__main__.: 

更多信息,可以发现here

希望这可以帮助。

+0

Heya,我已经添加了一个新的答案,提供100%的测试覆盖率(测试!),并且不需要忽略任何东西。让我知道你的想法:http://stackoverflow.com/a/27084447/1423157谢谢。 – robru 2014-11-23 23:59:38

+0

对于那些想知道:'nose-cov'在下面使用coverage.py,所以具有上述内容的'.coveragerc'文件将工作得很好。 – Joscha 2015-11-12 04:58:37

+3

恕我直言,即使我发现它有趣和有用,这个答案不*实际给予OP的回应。他想要测试主叫,不要跳过这个检查。否则,脚本实际上可以完成除了实际预期的任何事情以外的所有事情,并且测试会显示“OK,一切正常!”。主要功能可以完全通过单元测试,即使从未被实际调用过。 – iacopo 2017-02-05 10:25:24

9

您可以使用imp模块,而不是import语句做到这一点。 import声明的问题是'__main__'的测试作为导入声明的一部分运行,然后您有机会分配到runpy.__name__

例如,你可以使用imp.load_source()像这样:

import imp 
runpy = imp.load_source('__main__', '/path/to/runpy.py') 

的第一个参数被分配到导入模块的__name__

+2

imp模块似乎很像我在问题中使用的runpy模块。问题在于模块无法(显然)在模块加载之后和代码运行之前插入。你有什么建议吗? – Nikolaj 2011-05-01 19:54:40

2

一种方法是将模块作为脚本运行(例如os.system(...)),并将它们的stdout和stderr输出与预期值进行比较。

+1

在子流程中运行脚本并期待coverage.py跟踪所执行的行并不容易,因为它听起来很容易,因此可以在此处找到有关使此解决方案起作用的更多信息:http://nedbatchelder.com/code/coverage/ subprocess.html – mouad 2011-05-01 18:37:57

4

哇,我有点迟到了,但我最近就遇到了这个问题,我想,我想出了一个更好的解决方案,所以这里是......

我工作的一个包含有十几个剧本都与此完全相同copypasta结束模块:

if __name__ == '__main__': 
    if '--help' in sys.argv or '-h' in sys.argv: 
     print(__doc__) 
    else: 
     sys.exit(main()) 

不可怕,肯定的,但不可测试无论是。我的解决办法是在我的模块中的一个写一个新功能:

def run_script(name, doc, main): 
    """Act like a script if we were invoked like a script.""" 
    if name == '__main__': 
     if '--help' in sys.argv or '-h' in sys.argv: 
      sys.stdout.write(doc) 
     else: 
      sys.exit(main()) 

,然后把这种宝石在每个脚本文件的末尾:

run_script(__name__, __doc__, main) 

从技术上讲,此功能将无条件是否运行您的脚本作为模块导入或作为脚本运行。这是好的,但因为该功能实际上并不任何东西,除非该脚本正在作为脚本运行。因此,代码覆盖率看到函数运行,并说“是的,100%的代码覆盖率!”同时,我写了三个测试覆盖本身的功能:

@patch('mymodule.utils.sys') 
def test_run_script_as_import(self, sysMock): 
    """The run_script() func is a NOP when name != __main__.""" 
    mainMock = Mock() 
    sysMock.argv = [] 
    run_script('some_module', 'docdocdoc', mainMock) 
    self.assertEqual(mainMock.mock_calls, []) 
    self.assertEqual(sysMock.exit.mock_calls, []) 
    self.assertEqual(sysMock.stdout.write.mock_calls, []) 

@patch('mymodule.utils.sys') 
def test_run_script_as_script(self, sysMock): 
    """Invoke main() when run as a script.""" 
    mainMock = Mock() 
    sysMock.argv = [] 
    run_script('__main__', 'docdocdoc', mainMock) 
    mainMock.assert_called_once_with() 
    sysMock.exit.assert_called_once_with(mainMock()) 
    self.assertEqual(sysMock.stdout.write.mock_calls, []) 

@patch('mymodule.utils.sys') 
def test_run_script_with_help(self, sysMock): 
    """Print help when the user asks for help.""" 
    mainMock = Mock() 
    for h in ('-h', '--help'): 
     sysMock.argv = [h] 
     run_script('__main__', h*5, mainMock) 
     self.assertEqual(mainMock.mock_calls, []) 
     self.assertEqual(sysMock.exit.mock_calls, []) 
     sysMock.stdout.write.assert_called_with(h*5) 

布拉姆!现在您可以编写一个可测试的main(),将其作为脚本调用,具有100%的测试覆盖率,并且不需要忽略覆盖报告中的任何代码。

+8

I欣赏寻找解决方案的创造力和毅力,但是如果你在我的团队中,我会否决这种编码方式.Python的一个优点是它具有高度的惯用性,'if __name__ == ...'是**这种方式让模块脚本,任何pythonista都会识别这行,并理解它的作用,你的解决方案只是混淆了明显的原因,没有什么好的理由,除了搔痒智力瘙痒外如我所说:一个聪明的解决方案,但_clever_并不总是等于_correct_。 – mac 2015-03-09 09:47:39

+0

这很好,如果你只有一个模块,或者每个模块在作为脚本被调用时都会有不同的结果,但正如我所说的,如果__name__ ==有十几个*完全相同的文件。 ..'块在最后,这是一个巨大的违反不重复自己当你需要在很多地方对它进行相同的修复时,d也使修复错误变得困难。统一这样的逻辑提高了可测试性并减少了错误的可能性。如果您担心人们不理解它,请将该函数命名为“if_name_equals_main()”,并且人们会将其计算出来。 – robru 2015-03-10 17:21:18

+7

如果你在'if __name__ ...'下缩进的块中有**逻辑**,那么你做错了,应该重构。 'if __name __...'下的唯一代码行应该是:'main()'。 – mac 2015-03-10 23:04:14

相关问题