如何模拟 Python 方法 OptionParser.error(),该方法执行 sys.exit()?

15

我正在尝试对类似以下代码进行单元测试:

def main():
    parser = optparse.OptionParser(description='This tool is cool', prog='cool-tool')
    parser.add_option('--foo', action='store', help='The foo option is self-explanatory')
    options, arguments = parser.parse_args()
    if not options.foo:
        parser.error('--foo option is required')
    print "Your foo is %s." % options.foo
    return 0

if __name__ == '__main__':
   sys.exit(main())

使用类似这样的代码:

@patch('optparse.OptionParser')
def test_main_with_missing_p4clientsdir_option(self, mock_optionparser):
    #
    # setup
    #
    optionparser_mock = Mock()
    mock_optionparser.return_value = optionparser_mock
    options_stub = Mock()
    options_stub.foo = None
    optionparser_mock.parse_args.return_value = (options_stub, sentinel.arguments)
    def parser_error_mock(message):
        self.assertEquals(message, '--foo option is required')
        sys.exit(2)
    optionparser_mock.error = parser_error_mock

    #
    # exercise & verify
    #
    self.assertEquals(sut.main(), 2)

我正在使用Michael Foord's Mock和nose来运行测试。
当我运行测试时,我得到了以下结果:
  File "/Users/dspitzer/Programming/Python/test-optparse-error/tests/sut_tests.py", line 27, in parser_error_mock
    sys.exit(2)
SystemExit: 2

----------------------------------------------------------------------
Ran 1 test in 0.012s

FAILED (errors=1)

问题在于OptionParser.error会执行sys.exit(2),所以main()自然依赖于它。但是nose或unittest检测到了(预期的)sys.exit(2),并且导致测试失败。
我可以通过在main()中的parser.error()调用下添加"return 2"并从parser_error_mock()中删除sys.exit()调用来使测试通过,但我觉得修改被测试代码以使测试通过是不好的做法。有更好的解决方案吗?
更新:df的答案可行,但正确的调用是"self.assertRaises(SystemExit, sut.main)"。
这意味着测试无论parser_error_mock()中的sys.exit()中的数字是什么都会通过。有没有办法测试退出代码?
顺便说一句,如果我添加以下内容,测试会更加健壮:
self.assertEquals(optionparser_mock.method_calls, [('add_option', ('--foo',), {'action': 'store', 'help': 'The foo option is self-explanatory'}), ('parse_args', (), {})])
更新2:我可以通过将“self.assertRaises(SystemExit,sut.main)”替换为以下内容来测试退出代码:

在结尾处。

try:
    sut.main()
except SystemExit, e:
    self.assertEquals(type(e), type(SystemExit()))
    self.assertEquals(e.code, 2)
except Exception, e:
    self.fail('unexpected exception: %s' % e)
else:
    self.fail('SystemExit exception expected')

2
你的Update 2中的第一个assertEquals是不必要的,因为它上面的"except"行只会捕获SystemExit异常。 - rbp
4个回答

12

这个能否代替assertEquals

self.assertRaises(SystemExit, sut.main, 2)

这将捕获 SystemExit 异常并防止脚本终止。


我取消了你的回答,因为它不是完全正确的。但是既然你给了我启示,让我找到了答案(请参见问题的更新),我会给你几天时间来编辑你的回答,然后我会接受它。 - Daryl Spitzer

1

正如我在更新我的问题时指出的那样,我不得不修改dF的答案:

self.assertRaises(SystemExit, sut.main)

...然后我想出了一些更长的代码片段来测试退出代码。

[注意:我接受了自己的答案,但如果dF更新了他的答案,我将删除这个答案并接受他的答案。]


0
在你的测试函数中添加@raises。例如:
from nose.tools import raises

@raises(SystemExit)
@patch('optparse.OptionParser')
def test_main_with_missing_p4clientsdir_option(self, mock_optionparser):
    # Setup
    ...
    sut.main()

0

我快速阅读了一下,虽然它是同样的问题(在Java中),但我没有看到任何适用于Python的内容。你有吗? - Daryl Spitzer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接