python, unittest: is there a way to pass command line options to the app

PythonUnit Testing

Python Problem Overview


I have a module that imports unittest and has some TestCases. I would like to accept some command-line options (for example below, the name of a data file), but when I try to pass the option I get the message option -i not recognized. Is it possible to have unittest + provide options to the app (note: I'm using optparse to handle the options)? Thanks.

$ python test_app_data.py -i data_1.txt

option -i not recognized

=====================

follow-up: this is an implementation of the suggested solution:

import cfg_master  #has the optparse option-handling code

...

if __name__ == '__main__':    
    #add you app's options here...
    options_tpl = ('-i', '--in_dir', '-o', '--out_dir')
    del_lst = []
    for i,option in enumerate(sys.argv):
        if option in options_tpl:
            del_lst.append(i)
            del_lst.append(i+1)

    del_lst.reverse()
    for i in del_lst:
        del sys.argv[i]
        
    unittest.main()

Python Solutions


Solution 1 - Python

Building on Alex's answer, it's actually pretty easy to do using argparse:

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--input', default='My Input')
    parser.add_argument('filename', default='some_file.txt')
    parser.add_argument('unittest_args', nargs='*')
    
    args = parser.parse_args()
    # TODO: Go do something with args.input and args.filename

    # Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone)
    sys.argv[1:] = args.unittest_args
    unittest.main()

I haven't tested all of the flags you can pass into unittest to see if they work or not, but passing test names in does work, e.g.:

python test.py --input=foo data.txt MyTest

Runs MyTest with foo and data.txt.

Solution 2 - Python

In your if __name__ == '__main__': section, which you're not showing us, you'll need to optparse and then del sys.argv[1:] before you pass control to unittest code, so that the latter code doesn't try to interpret your command line options again when you've already dealt with them. (It's a bit harder to have some options of your own and also pass some down to unittest, though it can be done if you do have such complex needs).

Solution 3 - Python

I thought I'd share my solution for adding a --debug switch to the test to control the logger:

if __name__=='__main__':
     parser = argparse.ArgumentParser(description="Build a compilation script")
     parser.add_argument('--debug', help='Turn on debug messages', action='store_true', default=False)

 	 args = parser.parse_args()

     if args.debug:
	     log_level = logging.DEBUG
     else:
	     log_level = logging.INFO

     logging.basicConfig(level=log_level)
	 sys.argv.pop()
     unittest.main()

Then I extended unittest.TestCase to add logging:

 class mcs_TestCase(unittest.TestCase, object):
     def __init__(self, *args, **kwargs):
         super(mcs_TestCase,self).__init__(*args,**kwargs)
         logging.basicConfig()
         self.logger = logging.getLogger(__name__)
         ...

Now I can turn messages on and off in my test using --debug, but it gets ignored in regular regressions.

Solution 4 - Python

For small standalone apps, I use an initial sentinel option (-t) and call unittest.main() before calling argparse.ArgumentParser()

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] in ["-t", "--test"]:
        del(sys.argv[1])
        sys.exit(unittest.main()) # pass sys.argv[

    p = argparse.ArgumentParser()
    . . .

Solution 5 - Python

In addition to the other answers I'd like to provide a different view on the question. I know the question asked for a way to provide command line options to unit tests, however the actual problem that is stated is just "how can I pass parameters to unit tests".

This can also be achieved with environment variables.

Here's an example how you could provide different data to a test locally and on a build server:

my-script.py:

from os import environ

database = MyDatabase(
    user = 'root',
    password = 'some-pw',
    host = environ['MYSQL_HOST'],
    port = environ['MYSQL_TCP_PORT']
)

test-local.sh:

MYSQL_TCP_PORT="3311"
MYSQL_HOST="localhost"
python3.7 -m unittest

test-ci.sh:

MYSQL_TCP_PORT="3306"
MYSQL_HOST="my-database"
python3.7 -m unittest

Solution 6 - Python

You should not take arguments and options to run unittests, as you make them run under different, less predictable conditions this way. You should figure out why you need to run tests with different data, and make you test suite complete enough to cover the ground of all data sets without being run differently each time.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionjd.View Question on Stackoverflow
Solution 1 - PythontghwView Answer on Stackoverflow
Solution 2 - PythonAlex MartelliView Answer on Stackoverflow
Solution 3 - PythonRay SalemiView Answer on Stackoverflow
Solution 4 - PythonqneillView Answer on Stackoverflow
Solution 5 - PythonberslingView Answer on Stackoverflow
Solution 6 - PythonironfroggyView Answer on Stackoverflow