Search code examples
pythonargparsepython-unittest

Unittest has unrecognized arguments


I'm running unit tests for my program.py file. I found an error: python3 -m unittest: error: unrecognized arguments:.

I believe the error comes from the argparse library I'm using where the target code is expecting some arguments.

Target file: program.py:

import argparse

parse = argparse.ArgumentParser(description="Command line program.")
parse.add_argument("--key", type=str,
                   help="Enter key")
parse.add_argument("--output", type=str,
                   help="Path to place results.")
args = parse.parse_args()


def program():
    # Use args here 

def writefile():
    # Uses args and write to file 

if __name__ == "__main__":
    program()

Test file: program_test.py:

import unittest

import program

class TestProgram(unittest.TestCase):
    def setUp(self):
        self.argv_list = ["--key", "somefile.txt",
                          "--output", "myresultfile.txt"]

    def test_program_stuff(self):
        # See "Things I've tried" 
        program.writefile(...)

Command:

me@mylinux:myprogram$ env/bin/python3 -m unittest -v program_test.py 

usage: python3 -m unittest [-h] [--key KEY] [--output OUTPUT]
python3 -m unittest: error: unrecognized arguments: -v program_test.py

Things I've tried:

  • Mock the argparse.Namespace with argparse.Namespace(key="key.txt", output="result.txt")
  • Manipulate sys.args in test_program_stuff by sys.args.append(self.argv_list)

I've looked at solutions to unit testing argparse but none have helped so I'm thinking it may not be the same issue:


Solution

  • Note: I do realize that this is a duplicate of Pytest unrecognized arguments when importing file with argparse, however, that question was unfortunately not answered. It is also a duplicate of How to call function with argparse arguments in unittests?, however, he doesn't want to provide arguments and instead wants to call another function defined in the module.


    Essentially, the problem can be reduced to the following:

    # main.py
    
    import argparse
    
    parse = argparse.ArgumentParser()
    parse.add_argument("--foo", action="store_true")
    
    args = parse.parse_args()
    

    and

    # tests.py
    
    import main
    

    If we run that with python -m unittest ./tests.py, we receive the following output:

    usage: python -m unittest [-h] [--foo FOO]
    python -m unittest: error: unrecognized arguments: ./tests.py
    

    The problem is that if you import something, all the top level code will run during the import. Usually, this isn't a problem because in a library you only really define functions to be used by other programs, however, in your case the parse.parse_args() runs.

    This can be resolved by guarding this logic similar to what you already did:

    import argparse
    
    def main():
        parse = argparse.ArgumentParser()
        parse.add_argument("--foo")
    
        args = parse.parse_args()
    
    if __name__ == "__main__":
        main()
    

    Here, __name__ will contain the name of the module which would be "main" if it is imported or "__main__" if it is run directly. Therefore, the main() function will not be called during the unit test. Read more about this here.


    However, in your specific case, it seems that you want to test the functionality of the main() function (or rather the code that you have in your main module.)

    There are generally two ways to achieve this:

    1. You can simply accept the arguments in main():

      import argparse
      import sys
      
      def main(argv):
          parse = argparse.ArgumentParser()
          parse.add_argument("--foo", action="store_true")
      
          args = parse.parse_args(argv[1:])
      
      if __name__ == "__main__":
          main(sys.argv)
      

      Then you are able to provide these arguments in the unit test as well:

      import unittest
      
      import main
      
      class Tests(unittest.TestCase):
          def test_whatever(self):
              main.main(["main.py", "--foo"])
      
    2. You can use a mock-framework to change the value of sys.argv. In this situation would seem a bit over-engineered. However, if you are interested, that is answered here.