Going off of Greg Haskin's answer in this question, I tried to make a unittest to check that argparse is giving the appropriate error when I pass it some args that are not present in the choices
. However, unittest
generates a false positive using the try/except
statement below.
In addition, when I make a test using just a with assertRaises
statement, argparse
forces the system exit and the program does not execute any more tests.
I would like to be able to have a test for this, but maybe it's redundant given that argparse
exits upon error?
#!/usr/bin/env python3
import argparse
import unittest
class sweep_test_case(unittest.TestCase):
"""Tests that the merParse class works correctly"""
def setUp(self):
self.parser=argparse.ArgumentParser()
self.parser.add_argument(
"-c", "--color",
type=str,
choices=["yellow", "blue"],
required=True)
def test_required_unknown_TE(self):
"""Try to perform sweep on something that isn't an option.
Should return an attribute error if it fails.
This test incorrectly shows that the test passed, even though that must
not be true."""
args = ["--color", "NADA"]
try:
self.assertRaises(argparse.ArgumentError, self.parser.parse_args(args))
except SystemExit:
print("should give a false positive pass")
def test_required_unknown(self):
"""Try to perform sweep on something that isn't an option.
Should return an attribute error if it fails.
This test incorrectly shows that the test passed, even though that must
not be true."""
args = ["--color", "NADA"]
with self.assertRaises(argparse.ArgumentError):
self.parser.parse_args(args)
if __name__ == '__main__':
unittest.main()
Errors:
Usage: temp.py [-h] -c {yellow,blue}
temp.py: error: argument -c/--color: invalid choice: 'NADA' (choose from 'yellow', 'blue')
E
usage: temp.py [-h] -c {yellow,blue}
temp.py: error: argument -c/--color: invalid choice: 'NADA' (choose from 'yellow', 'blue')
should give a false positive pass
.
======================================================================
ERROR: test_required_unknown (__main__.sweep_test_case)
Try to perform sweep on something that isn't an option.
----------------------------------------------------------------------
Traceback (most recent call last): #(I deleted some lines)
File "/Users/darrin/anaconda/lib/python3.5/argparse.py", line 2310, in _check_value
raise ArgumentError(action, msg % args)
argparse.ArgumentError: argument -c/--color: invalid choice: 'NADA' (choose from 'yellow', 'blue')
During handling of the above exception, another exception occurred:
Traceback (most recent call last): #(I deleted some lines)
File "/anaconda/lib/python3.5/argparse.py", line 2372, in exit
_sys.exit(status)
SystemExit: 2
If you look in the error log, you can see that a
argparse.ArgumentError
is raised and not anAttributeError
. your code should look like this:If you look into the source code of argparse, in
argparse.py
, around line 1732 (my python version is 3.5.1), there is a method ofArgumentParser
calledparse_known_args
. The code is:So, the
ArgumentError
will be swallowed byargparse
, and exit with an error code. If you want to test this anyway, the only way I could think out is mockingsys.exc_info
.The trick here is to catch
SystemExit
instead ofArgumentError
. Here's your test rewritten to catchSystemExit
:That now runs correctly, and the test passes:
However, you can see that the usage message is getting printed, so your test output is a bit messed up. It might also be nice to check that the usage message contains "invalid choice".
You can do that by patching
sys.stderr
:Now you only see the regular test report:
For pytest users, here's the equivalent that doesn't check the message.
Pytest captures stdout/stderr by default, so it doesn't pollute the test report.
You can also check the stdout/stderr contents with pytest:
As usual, I find pytest easier to use, but you can make it work in either one.
I had a similar problem with the same error of argparse (exit 2) and corrected it capturing the first element of tuple that parse_known_args() return, an argparse.Namespace object.
While the parser may raise an ArgumentError during parsing a specific argument, that is normally trapped, and passed to
parser.error
andparse.exit
. The result is that the usage is printed, along with an error message, and thensys.exit(2)
.So
asssertRaises
is not a good way of testing for this kind of error inargparse
. The unittest file for the module,test/test_argparse.py
has an elaborate way of getting around this, the involves subclassing theArgumentParser
, redefining itserror
method, and redirecting output.parser.parse_known_args
(which is called byparse_args
) ends with:=================
How about this test (I've borrowed several ideas from
test_argparse.py
:with a run: