如何测试我的代码是不是引发了适当的 argparse 异常?

Posted

技术标签:

【中文标题】如何测试我的代码是不是引发了适当的 argparse 异常?【英文标题】:How can I test whether my code is throwing the appropriate argparse exceptions?如何测试我的代码是否引发了适当的 argparse 异常? 【发布时间】:2017-04-15 09:20:58 【问题描述】:

从this great answer 我学会了将参数解析放到它自己的函数中来简化单元测试。

从this answer 我了解到,有时您需要抛出自己的解析器错误才能让 argparse 执行您想要的行为。例如:

if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')

但是很难测试它是否能做到它应该做的,因为抛出解析器错误也会退出程序。所以像这样的 TestCase 是行不通的:

def test_no_action_error(self):
    '''Test if no action produces correct error'''
    with self.assertRaises(ArgumentError) as cm:
        args = parse_args(' ')
    self.assertEqual('No action requested, add -process or -upload', str(cm.exception))

第一个问题的 cmets 建议 this question。但我不了解如何在测试文件中使用此代码。

【问题讨论】:

@wim 没有错,我只是在 SO 上找不到一个问题,说这样做(以及如何) 【参考方案1】:

经过一番修改后,我发现了可以通过测试的东西。删除不受欢迎的建议。

在我的主程序中,我定义了parse_args,并带有一些额外的关键字参数,仅用于测试。

def parse_args(args, prog = None, usage = None):
    PARSER = argparse.ArgumentParser(prog=prog, usage=usage)
    ....

然后在用于测试解析器的测试类中,添加这些参数以尽可能抑制错误的使用和帮助信息。

class ArgParseTestCase(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        self.testing_params = 'prog':'TESTING', 'usage':''
        super(ArgParseTestCase, self).__init__(*args, **kwargs)

在测试文件中从this answer定义了这个上下文管理器:

from contextlib import contextmanager
from io import StringIO

@contextmanager
def capture_sys_output():
    capture_out, capture_err = StringIO(), StringIO()
    current_out, current_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = capture_out, capture_err
        yield capture_out, capture_err
    finally:
        sys.stdout, sys.stderr = current_out, current_err

然后将我上面问题中的测试修改为:

def test_no_action_error(self):
    '''Test if no action produces correct error'''
    with self.assertRaises(SystemExit) as cm, capture_sys_output() as (stdout, stderr):
        args = parse_args([' '], **self.testing_params)
    self.assertEqual(2, cm.exception.code)
    self.assertEqual('usage: \n TESTING: error: No action requested, add -process or -upload',
                     stderr.getvalue())

现在assertEqual 开头的额外文本并不漂亮......但测试通过了,所以我很高兴。

【讨论】:

【参考方案2】:

test/test_argparse.py 做了一些这样的测试:

例如:

class TestArgumentTypeError(TestCase):

    def test_argument_type_error(self):

        def spam(string):
            raise argparse.ArgumentTypeError('spam!')

        parser = ErrorRaisingArgumentParser(prog='PROG', add_help=False)
        parser.add_argument('x', type=spam)
        with self.assertRaises(ArgumentParserError) as cm:
            parser.parse_args(['XXX'])
        self.assertEqual('usage: PROG x\nPROG: error: argument x: spam!\n',
                         cm.exception.stderr)

但关键是在文件开头附近定义的ErrorRaisingArgumentParser 子类。

class ErrorRaisingArgumentParser(argparse.ArgumentParser):

    def parse_args(self, *args, **kwargs):
        parse_args = super(ErrorRaisingArgumentParser, self).parse_args
        return stderr_to_parser_error(parse_args, *args, **kwargs)

    def exit(self, *args, **kwargs):
        exit = super(ErrorRaisingArgumentParser, self).exit
        return stderr_to_parser_error(exit, *args, **kwargs)

    def error(self, *args, **kwargs):
        error = super(ErrorRaisingArgumentParser, self).error
        return stderr_to_parser_error(error, *args, **kwargs)

有关详细信息,请参阅该文件。使用 stderr 重定向它会变得有点复杂。也许比真正需要的要多。

【讨论】:

我想我知道这可能会发生什么。但它似乎也涉及在代码和测试代码之间复制解析器的定义(第一个测试代码块向解析器添加参数)。 我没有选择与您的情况非常匹配的示例;它正在测试一种不同类型的错误。对于您的情况,有 2 个问题 - 捕获或重定向 sys.exit,并捕获随之而来的 stderr 消息。 我找到了适合我的解决方案,请参阅here【参考方案3】:

使用pytest 的最简单方法如下:

with pytest.raises(SystemExit) as e:
    parse_args(...)

assert isinstance(e.value.__context__, argparse.ArgumentError)
assert 'expected err msg' in e.value.__context__.message

我们需要这种解决方法,因为 argparse 将退出错误代码 2,这意味着将引发 SystemExit

【讨论】:

以上是关于如何测试我的代码是不是引发了适当的 argparse 异常?的主要内容,如果未能解决你的问题,请参考以下文章

如何测试调用socket.send时是不是引发异常[重复]

如何跟踪我的 Web 服务引发的 IIS 500 错误

在 python 中调试 argpars

由测试杯子,引发的思考

python argpare 模块的简单用法

Unity 单元测试 - 检查带有参数的函数调用是不是引发异常