I have a file conftest.py in directory A. I have some test files in directory B which is in A. I run all of them using py.test -sv B/. However sometime I want to pass argument -val="Hello" and store in a file.
I am trying to do it as following:
import argparse
parser=argparse.ArgumentParser()
parser.add_argument("val",help="value")
args=parser.parse_args()
print(args.val)
a_file=open("file_val","w")
a_file.write(args.val)
a.file.close()
def pytest_addoption(parser):
parser.addoption("--name", action="store", default="default name")
However it doesn't write anything and gives error: unrecognized arguments: -sv —val=hello, when I run py.test -sv B/ —val=Hello
so I tried following :
import argparse
def pytest_addoption(parser):
parser.addoption("--name", action="store", default="default name")
parser.add_argument("val",help="value")
args=parser.parse_args()
print(args.val)
a_file=open("file_val","w")
a_file.write(args.val)
a.file.close()
But it gives error : No method add_argument when I run py.test -sv B/ —val=Hello
read command line argument while running pytest -sv B/
It should work for you:
First of all you need to pass the argument using addoption and fixture that will read the option.
For that you should create a separate file and name it conftest.py in your test folder.
The content of the conftest.py file:
import pytest
def pytest_addoption(parser):
parser.addoption("--name", action="store", default="default name")
#pytest.fixture
def name(request):
return request.config.getoption("--name")
Then you need to create a test function with an argument which named after the fixture in the separate file, or like in your case, even in a separate folder.
Content of B/test_write_file.py:
def test_write_file(name):
print(name)
a_file = open("file_val.txt", "w")
a_file.write(name)
a_file.close()
assert 0 # or whatever you want
After that you can freely run your test from the test root folder:
pytest -sv B/ --name=Hello
Output:
________________ test_write_file _________________
name = 'Hello'
def test_write_file(name):
print(name)
a_file = open("file_val.txt", "w")
a_file.write(name)
a_file.close()
> assert 0
E assert 0
B\test_write_file.py:8: AssertionError
You don't need to use argparse, pytest does everything for you.
The structure of the test folder:
Just like in this example:
https://docs.pytest.org/en/6.2.x/example/simple.html
Cheers!
Related
I'm trying to customize pytest command line option parsing behavior to accept two groups of paramaters separated by --.
parameters that come before the separator as passed to pytest as is.
The parameters after the separator are to be stored in fixture for later use
For example in the following invocation,
pytest -s mytest.py -- --myarg 10
-s option is passed to pytest and the string --myarg 10 is stored as test_args fixture in mytest.py.
I have tried to customize pytest_load_initial_conftests to modify args.
% ls
args_plugin.py test_my_arg.py
% cat args_plugin.py
import pytest
from typing import List
_test_args: List[str] = []
def _setup_args(args: List[str]):
sep = "--"
if sep in args:
idx = args.index("--")
global _test_args
_test_args = args[idx + 1 :]
args = args[:idx]
return args
#pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args: List[str]):
args = _setup_args(args)
print(_test_args, args)
outcome = yield
#pytest.fixture
def cli_args() -> List[str]:
return _test_args
% cat test_my_args.py
import pytest
def test_cli_args(cli_args):
print(cli_args)
Running it, I get following error
% PYTHONPATH=. pytest -p args_plugin -s . -- --myarg 10
['--myarg', '10'] ['-p', 'args_plugin', '-s', '.']
======= test session starts =======
collected 0 items
======= no tests ran in 0.00s =======
ERROR: file or directory not found: --myarg
Any alternative approaches to the problem are also welcome.
If I understand your code right, I believe all you need is to add [:] to your args = expression in hook function.
Like this:
#pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args: List[str]):
args[:] = _setup_args(args)
print(_test_args, args)
outcome = yield
It will overwrite args variable instead of defining new one (in local function scope)
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)
I have written some scripts that I'm trying to integrate with click. All the scripts are written in python OOP's.
The issue is that i am trying to build command section in oop's way but couldn't do it properly.
let me show you, what i am trying to do and please note that i am sharing here dummy code it is very similar to the real code.
First thing the directory structure:
-customcmd <dir>
|
|->commands <dir>
| -> abc-command.py
| -> __init__.py
|
|->__init__.py
|->main.py
|->setup.py
1) I have created one file called main.py, which contains following code:
import click
import os
plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')
class MyCLI(click.MultiCommand):
def list_commands(self, ctx):
rv = []
for filename in os.listdir(plugin_folder):
if filename.startswith('__'):
continue
if filename.endswith('.py'):
rv.append(filename[:-3])
rv.sort()
return rv
def get_command(self, ctx, name):
ns = {}
fn = os.path.join(plugin_folder, name + '.py')
with open(fn) as f:
code = compile(f.read(), fn, 'exec')
eval(code, ns, ns)
return ns['cli']
cli = MyCLI()#help='This tool\'s subcommands are loaded from a ''plugin folder dynamically.'
if __name__ == '__main__':
cli()
2) abc-command.py
#click.command()
#click.option("--file-loc", '-fl', type=open, required=True, default=None, help="Path to the file")
def cli(file_loc):
"""
This is test command
"""
print("Path to file {}".format(file_loc))
Output of above code when you call main.py:
(automation) K:\Pythonenv\automation\customcmd>python main.py
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
policyresult This is test command
Output of above code when you call sub command:
(automation) K:\Pythonenv\automation\customcmd>python main.py policyresult --help
Usage: main.py policyresult [OPTIONS]
This is test command
Options:
-fl, --file-loc OPEN Path to the file [required]
--help Show this message and exit.
3) This is how I converted the procedural code of abc-command.py code:
class policyresult():
def __init__(self):
pass
#click.command()
#click.option("--file-loc", '-fl', type=open, required=True, default=None, help="Path to the file")
def cli(self,file_loc):
"""
This is test command
"""
print("Path to file {}".format(file_loc))
obj = policyresult()
obj.cli()
Output of above code doesn't match with the previous output when the code was procedural in abc-command.py:
Here i am calling the main.py
(automation) K:\Pythonenv\automation\customcmd>python main.py
Usage: main.py [OPTIONS]
Try "main.py --help" for help.
Error: Missing option "--file-loc" / "-fl".
In the above output you can see it is directly going into the sub-command options things and giving error as well.
As far as i understand list_commands() which is in main.py can't list out the commands, this part i can't understand why it is not working properly.
I tried various things but couldn't find the proper way to implement OOP's in abc-command.py because of that my ouput doesn't match.
I am new to this click framework, please suggest any new changes in my approach if needed.
please look into this, sorry for this weird way to explaining this.
abc-command.py is evaluated before click parses command options because of this line in the file invoking the cli method:
obj.cli()
Also, in the get_command method implemented for the multi-command, commands are supposed to expose a 'cli' name in their namespace.
To fix this error, in abc-command.py update the line invoking the cli command with:
cli = obj.cli
so that a cli name is exposed in abc-command.py module
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
Im trying to have interchangeable config files and I'm using py.test to send the testing suite to the cloud.
The following works when I run them locally using python main.py --site site1 in the terminal.
I'm trying to figure out how I can add cli arguments so that it will work with py.test
I have 3 files. main, config, and site1_config
main.py
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='CLI tests for an environment')
parser.add_argument('--site', nargs='?', help='the site to use for the config')
parser.add_argument('unittest_args', nargs='*')
#Get our property
args = parser.parse_args()
if args.site:
config.localizeConfig(args.site)
sys.argv[1:] = args.unittest_args
unittest.main(verbosity = 2)
config.py
def localizeConfig(site):
localizedConfig = __import__(site+'_config');
# localizedConfig = __import__(site);
#print dir(localizedConfig)
props = filter(lambda a: not a.startswith('__'), dir(localizedConfig))
#Iterate over each property and set it on the config
for prop in props:
if prop != 'os' and prop != 'sys' and prop != 'webdriver':
globals()[prop] = getattr(localizedConfig, prop)
host_url = www.google.com
site1_config.py
host_url = www.yahoo.com
Im trying to set a flag so that when py.test -n6 --boxed main.py site1
is run, the site1_config.py will copy over its contents into config.py
I'm not sure how I can get this to work with py.test
usage: py.test [options] [file_or_dir] [file_or_dir] [...]
py.test: error: unrecognized arguments:
I don't really get what you are trying to do, but in order to add CLI arguments to py.test check:
http://pytest.org/latest/example/simple.html:
# content of conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store", default="type1",
help="my option: type1 or type2")
The equivalent of your unittest.main(verbosity = 2) is pytest.main(['-v'])
Things to note:
In pytest, I'm not sure verbosity level is selectable
Some of the references on pytest.main(input) don't specify that the input has to be a list, but as of today it must be.
https://docs.pytest.org/en/latest/usage.html