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:
Python argparse "unrecognized arguments" error
argparse fails when called from unittest test
argparse and unittest python
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:
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"])
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.
The parse.parse_args is outside of a method so will get run when the file is imported by the unittest file. This is why it is complaining that it doesn't have the expected arguments.
A solution is to move the parse_args into a function. e.g:
import argparse
import sys
def parse_args(sys_args):
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.")
return parse.parse_args(sys_args)
def program(key, output):
# Use args here
use_key(key)
writefile(output)
def use_key(key):
print(f"I have the key: {key}")
def writefile(filename):
# Uses args and write to file
print(f"I will write to file: {filename}")
if __name__ == "__main__":
parsed_args = parse_args(sys.argv[1:])
program(parsed_args.key, parsed_args.output)
This then allows things to test individually. e.g:
import io
import unittest
from unittest import mock
import program
class TestProgram(unittest.TestCase):
def setUp(self):
self.argv_list = ["--key", "somefile.txt",
"--output", "myresultfile.txt"]
def test_program_parse(self):
# See "Things I've tried"
args = program.parse_args(self.argv_list)
self.assertEqual("somefile.txt", args.key)
self.assertEqual("myresultfile.txt", args.output)
def test_program(self):
with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
program.use_key('val1')
self.assertEqual('I have the key: val1\n', fake_out.getvalue())
def test_writefile(self):
with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
program.writefile('junk.txt')
self.assertEqual('I will write to file: junk.txt\n', fake_out.getvalue())
if __name__ == '__main__':
unittest.main(verbosity=2)
Related
I'm trying to call function_3 with an argument but i'm receiving a error unrecognized arguments. I'm calling this way: python script.py --pass test
import argparse
import sys
def function_1():
print('Function 1')
def function_2():
print('Function 2')
def function_3(arg):
print(f'Function 3 {arg}')
if __name__ == "__main__":
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_f1 = subparsers.add_parser('fc1', help='Function 1')
parser_f1.set_defaults(func=function_1)
parser_f2 = subparsers.add_parser('fc2', help='Function 2')
parser_f2.set_defaults(func=function_2)
parser_f3 = subparsers.add_parser('fc3', help='Function 3')
parser_f3.add_argument("pass", help="function 3 argument")
parser_f3.set_defaults(func=function_3)
if len(sys.argv) <= 1:
sys.argv.append('--help')
options = parser.parse_args()
options.func()
Error
usage: argtest.py [-h] {fc1,fc2,fc3} ...
argtest.py: error: unrecognized arguments: --arg
First, your pass option is only available to the fc3 subparser, so rather than python script.py --pass test, you would want:
python script.py fc3 --pass test
But you've defined a positional argument, not a command line option. You need to either call your script like this:
python script.py fc3 test
Or you need to fix your code:
parser_f3.add_argument('--pass', help='function 3 argument')
That would allow you run python script.py fc3 --pass test.
There are still additional problems with the code; your function3 function requires an argument, but you're calling options.func() with no arguments, which will result in a TypeError exception.
I don't suggest doing it this way, I'd personally suggest just using click instead, but this is one method for doing it I guess...
import sys
from argparse import ArgumentParser
def parse_args():
parser = ArgumentParser()
parser.add_argument(
"-f", "--function", type=str, help="name of the function to call", default="echo"
)
parser.add_argument("func_args", nargs="*")
return parser.parse_args(args=None if sys.argv[1:] else ["--help"])
def echo(*args):
print(' '.join(*args))
if __name__ == "__main__":
args = parse_args()
eval(f"{args.function}({args.func_args})")
This approach feels fragile and wrong to me, but I believe this is line with the behavior you're asking for.
How do I pass command line arge to runpy.
I.e. if I can do python3 -m foo --arg1=abc
how can I add --arg1=abc as sys.argv[1] when launching the process like:
import runpy
runpy.run_module('foo')
Note, a question on this subject exists here: pass command-line arguments to runpy bu the author was trying to do something else and it was never answered.
Use standard python library: argrparse
For sake of example I've static coded runpy class
from argparse import ArgumentParser as ArgParser
class runpy:
#staticmethod
def run_module(arg):
print(arg)
if __name__ == '__main__':
description = (
'runpy commandline args')
parser = ArgParser(description=description)
parser.add_argument('--arg1', default="foo", type=str,
help='help text')
options = parser.parse_args()
runpy.run_module(options.arg1)
There is a part of the code in the file fastq_trimmer.py:
if __name__ == '__main__':
usage = '''
Fastq Trimmer CLI.
Usage:
fastq_trimmer.py trim <filePath> <trimFactor>
'''
args = docopt(usage)
if args['trim']:
commenceOperation(args)
else:
print(usage)
Im trying to write a unittest :
import fastq_trimmer
from docopt import docopt
doc = fastq_trimmer.commenceOperation(args)
class TestTrimmer(unittest.TestCase):
def test_exceptions(self):
args = docopt(doc, ["/home/eliran/Desktop/example.fastq", "5"])
self.assertEqual(args["<filePath>"], "/home/eliran/Desktop/example.fastq")
self.assertEqual(args["<trimFactor>"], "5")
basically im trying to control the args variable from the unittest file so I can 'inject' the CLI with the args i specify in the unittest file.
I am passing a single, positional argument string called FILE, but when no arguments are passed, I want it to print a usage statement.
Every time I write './files.py' in my command-line with no arguments after it, my code does nothing. What am I doing wrong?
import argparse
import re
#--------------------------------------------------
def get_args():
"""get arguments"""
parser = argparse.ArgumentParser(
description='Create Python script',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('FILE', help='Pass a file', type=str)
return parser.parse_args()
#--------------------------------------------------
def main():
"""main"""
args = get_args()
FILE = args.FILE.IGNORECASE()
if len(args) != 1:
print("Usage: files.py {}".format(FILE))
sys.exit(1)
# --------------------------------------------------
if __name__ == '__main__':
main()
Expected outcome:
$ ./files.py
Usage: files.py FILE
What I am getting:
$./files.py
$
You never run main...
import argparse
import re
#--------------------------------------------------
def get_args():
"""get arguments"""
parser = argparse.ArgumentParser(
description='Create Python script',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('FILE', help='Pass a file', type=str)
return parser.parse_args()
#--------------------------------------------------
def main():
"""main"""
args = get_args()
FILE = args.FILE.IGNORECASE()
if len(args) != 1:
print("Usage: files.py {}".format(FILE))
sys.exit(1)
main()
You need to define the entry point of your code. If you want to call this as you are describing (./files.py) you need to define the main entry point like this:
if __name__ == "__main__":
"""main"""
args = get_args()
FILE = args.FILE.IGNORECASE()
if len(args) != 1:
print("Usage: files.py {}".format(FILE))
sys.exit(1)
You have to tell your operating system that the script must be executed by Python. Add a shebang as the first line of your script:
#!/usr/bin/env python3
import argparse
...
Otherwise, you have to explicitly execute the script with Python:
python3 ./files.py
You must call your main function. A good place is at the end of the script, guarded to be run on execution only:
if __name__ == '__main__': # do not run on import
main()
This gives the desired output:
$ python3 so_script.py
usage: so_script.py [-h] FILE
so_script.py: error: the following arguments are required: FILE
Note that argparse already creates the usage and help messages for you. There is no need to create them yourself. In fact, argparse will end your script before your own usage information is run.
If you do not want to have the -h switch, pass add_help=False when creating the argument parser.
parser = argparse.ArgumentParser(
description='Create Python script',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False,
)
I am creating a test case in python using unittest module.
I did create a parsing argument list that i want to get from user.
But when i use that argument while executing the python script, it gives error: "option -i not recognized
Usage: testing.py [options] [test] [...]"
code snippet:
class Testclass(unittest.TestCase):
#classmethod
def setUpClass(cls):
print "Hello Class"
def test_addnum(self):
print "Execute the test case"
#parser = parse_args(['-i'])
print 'simple_value =', args.inputfile
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
ns, args = parser.parse_known_args(namespace=unittest)
#args = parser.parse_args()
return ns, sys.argv[:1] + args
if __name__ == '__main__':
unittest.main()
The error m getting on executing the above script with -i somefile.txt is:
option -i not recognized
Usage: testing.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
Examples:
testing.py - run default set of tests
testing.py MyTestSuite - run suite 'MyTestSuite'
testing.py MyTestCase.testSomething - run MyTestCase.testSomething
testing.py MyTestCase - run all 'test*' test methods
in MyTestCase
Any help would be appreciated.
This script captures the -i command, while still allowing unittest.main to do its own commandline parsing:
import unittest
class Testclass(unittest.TestCase):
#classmethod
def setUpClass(cls):
print "Hello Class"
def test_addnum(self):
print "Execute the test case"
#parser = parse_args(['-i'])
print 'simple_value =', args.inputfile
import argparse
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
ns, args = parser.parse_known_args(namespace=unittest)
#args = parser.parse_args()
return ns, sys.argv[:1] + args
if __name__ == '__main__':
import sys
args, argv = parse_args() # run this first
print(args, argv)
sys.argv[:] = argv # create cleans argv for main()
unittest.main()
produces:
1113:~/mypy$ python stack44236745.py -i testname -v
(<module 'unittest' from '/usr/lib/python2.7/unittest/__init__.pyc'>, ['stack44236745.py', '-v'])
Hello Class
test_addnum (__main__.Testclass) ... Execute the test case
simple_value = testname
ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
It looks rather kludgy, but does seem to work.
The idea is to run your own parser first, capturing the -i input, and putting the rest back into sys.argv. Your definition of parse_args suggests that you are already trying to do that.
Thanks hpaulj, your solution really helped me and I found one more solution for this problem. Hope it helps someone else facing the same issue.
import unittest
import argparse
import sys
class Testclass(unittest.TestCase):
#classmethod
def setUpClass(cls):
print "Hello Class"
def test_addnum(self):
print "Execute the test case"
#parser = parse_args(['-i'])
print 'simple_value =', args.inputfile
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
parser.add_argument('unittest_args', nargs='*')
args = parser.parse_args()
sys.argv[1:] = args.unittest_args
unittest.main()
Now executing the script with option -i as python testing.py -i somefile.txt gives result as
Hello Class
Execute the test case
simple_value = somefile.txt
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Your code is setting up your argument parser using
argparse.ArgumentParser()
parser.add_argument('-i', help='input file', dest='inputfile')
in a method of your test class. But I see no indication that the code is actually calling the method.
So at the time you start the program, the parser does not yet exist, because the method TestClass.parse_args() hasn't been called yet.
Move the creation of the parser and specification of its parameters out of the class so that the code calls it when the program starts.
You are running unittest.main() as the main program. So it's complaining rightfully that it does not know about the option you wrote:
"option -i not recognized"
If you want to create your own test suite launcher you should look into
TextTestRunner