如何测试我的代码是否抛出适当的 argparse 异常?
How can I test whether my code is throwing the appropriate argparse exceptions?
从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))
第一个问题的评论建议 this question。但我不了解如何在测试文件中使用此代码。
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 重定向会变得有点复杂。也许比真正需要的更多。
经过一番修改后,我发现了一些可以通过测试的东西。欢迎提出删除 cruft 的建议。
在我的主程序中,我用一些额外的关键字参数定义了 parse_args
,仅用于测试。
def parse_args(args, prog = None, usage = None):
PARSER = argparse.ArgumentParser(prog=prog, usage=usage)
....
然后在测试class 测试解析器时,添加这些参数来尽可能地抑制错误的使用和帮助信息。
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
开头的额外文本并不漂亮...但是测试通过了所以我很高兴。
使用 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
。
从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))
第一个问题的评论建议 this question。但我不了解如何在测试文件中使用此代码。
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 重定向会变得有点复杂。也许比真正需要的更多。
经过一番修改后,我发现了一些可以通过测试的东西。欢迎提出删除 cruft 的建议。
在我的主程序中,我用一些额外的关键字参数定义了 parse_args
,仅用于测试。
def parse_args(args, prog = None, usage = None):
PARSER = argparse.ArgumentParser(prog=prog, usage=usage)
....
然后在测试class 测试解析器时,添加这些参数来尽可能地抑制错误的使用和帮助信息。
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
开头的额外文本并不漂亮...但是测试通过了所以我很高兴。
使用 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
。