Skip to content Skip to sidebar Skip to footer

How Can I Test Whether My Code Is Throwing The Appropriate Argparse Exceptions?

From this great answer I learned to put argument parsing into its own function to simplify unit testing. From this answer I learned that sometimes you need to throw your own parser

Solution 1:

After a bit of hacking away I've found something that will pass testing. Suggestions to remove cruft welcome.

In my main program I defined parse_args with some extra keyword args to be used for testing only.

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

Then in the testing class for testing the parser, adding these parameters to suppress usage and help information on an error as much as possible.

classArgParseTestCase(unittest.TestCase):def__init__(self, *args, **kwargs):
        self.testing_params = {'prog':'TESTING', 'usage':''}
        super(ArgParseTestCase, self).__init__(*args, **kwargs)

In the testing file defined this context manager from this answer:

from contextlib import contextmanager
from io import StringIO

@contextmanagerdefcapture_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

And then modified the test in my question above to be something like:

deftest_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())

Now the extra text at the start of the assertEqual isn't pretty... but the test passes so I'm happy.

Solution 2:

test/test_argparse.py does some of this kind of testing:

For example:

classTestArgumentTypeError(TestCase):

    deftest_argument_type_error(self):

        defspam(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)

But the key to this the ErrorRaisingArgumentParser subclass defined near the start of the file.

classErrorRaisingArgumentParser(argparse.ArgumentParser):defparse_args(self, *args, **kwargs):
        parse_args = super(ErrorRaisingArgumentParser, self).parse_args
        return stderr_to_parser_error(parse_args, *args, **kwargs)

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

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

See that file for details. With stderr redirection it gets a bit complicated. Maybe more than really needed.

Solution 3:

The easiest way using pytest would be the following:

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

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

We need this workaround as argparse will exit error code 2 which means that a SystemExit will be raised.

Post a Comment for "How Can I Test Whether My Code Is Throwing The Appropriate Argparse Exceptions?"