I am trying to pass command line arguments to pytest tests.
Example from this question
print ("Displaying name: %s" % name)
In conftest
parser.addoption("--name", action="store", default="default name")
def pytest_generate_tests(metafunc):
# This is called for every test. Only get/set command line arguments
# if the argument is specified in the list of test "fixturenames".
option_value = metafunc.config.option.name
if 'name' in metafunc.fixturenames and option_value is not None:
metafunc.parametrize("name", [option_value])
I want to pass name argument to pytest.mark.parametrize.
Now, I am aware that according to the question I've referenced, the answer mentions that I shouldn't use #pytest.mark.parametrize. However, I am working on a code base that already utilizes it and I also need to pass arguments as part of CI process. Is there a work around or am I doomed to re-write all of the tests?
Edit:
Since I wasn't clear enough- I cannot use the command line argument in mark.parametrize with pytest console line args.
Example- I have a function that returns a list and has input for name and I add it to the mark.parametrize:
#pytest.mark.parametrize("return_list", foo(name)):
def test_name(return_list):
pass
The name parameter, like I mentioned needs to come from the command line, and this doesn't work in any method that I've encountered.
I don't know how to use a list in pytest without mark.parametrize, so it's a problem
Ok, so I've found an answer using pytest-lazy-fixture.
It can be pip installed for any version of pytest higher than 3.2.5 (and included).
The Gist is calling a custom fixture (conftest of the test itself) by its keyword.
Will leave a full answer here for future reference:
Full example:
In conftest
def pytest_addoption(parser):
parser.addoption("--name", action="store", default="default name")
#pytest.fixture(scope = 'module', autouse = True)
def names(request):
""" main code and manipulation on the data """
return request.config.getoption("--name")
In test.py
#pytest.mark.parametrize('arg1',
[pytest.lazy_fixture('names')]
)
def test_something(arg1):
# do something here
pass
This also works for mark.parametrize if the lazy_fixture is fed into another function.
Related
Whenever pytest is outputting the tests that it has, it always prints out the parameter values, but never the names of the variables.
For example:
PASSED test_create_patient.py::test_create_patient[False-True]
What I would like it to output is:
PASSED test_create_patient.py::test_create_patient[arg1=False-arg2=True]
Is this possible? I have tried various pytest flags like -vvv but nothing seems to work for this. It would be very helpful for debugging purposes.
I've looked into answers like this https://stackoverflow.com/a/18496751/8903959 but I don't understand them.
Generally, you can use the ids parameter in pytest.mark.parametrize to customize the parameter ids. In your case, this is inconvenient. While ids allows you to provide a custom function to adapt the output, this function only gets the parameter value as argument, not the parameter name, so you cannot use it to compose your wanted output. Instead, you would have to provide a list of ids:
#pytest.mark.parametrize("arg1, arg2", [(False, True), (True, False)],
ids=["arg1=False-arg2=True",
"arg1=True-arg2=False"])
def test_create_patient(arg1, arg2):
pass
To avoid this, you can use the hook function pytest_make_parametrize_id instead, which gets both the parameter name and the value. You have to put it in a conftest.py visible to your test. In your case, you can write:
conftest.py
def pytest_make_parametrize_id(config, val, argname):
return f"{argname}={val}"
and you would get the wanted notation for each test.
If you want to have this notation only for verbose output (say, with the option -vv or more), you can use config.option to check the verbosity and adapt the id accordingly:
conftest.py
def pytest_make_parametrize_id(config, val, argname):
if config.option.verbose >= 2: # -vv or -vvv
return f"{argname}={val}"
return repr(val) # the default
#given("employees informations are added", target_fixture="information_db")
def ensure_employees_informations_added():
return [_get_employee_information()]
My question is will the returned list be saved in "information_db" ? (I am new with working with pytest )
Looks like you are using pytest-bdd plugin in additional to bare pytest.
The plugin's documentation can be found at https://github.com/pytest-dev/pytest-bdd
Long story short, yes. The returned list will be saved under information_db name.
The next clause can access it by putting the target fixture's name into a list of the function's arguments:
#given("check employee")
def check_employee(information_db):
assert information_db[0]['skill'] == 42
I am using the Click library but I can't seem to find a behavior similar to dest from argparse.
For example, I have
#click.option('--format', type=click.Choice(['t', 'j']))
def plug(format):
pass
Notice that I am using a flag with --format that gets translated into a built-in Python construct format which is not ideal.
Is there a way to change the argument passed into the click function for options?
While Click doesn't have dest-equivalent of argparse, it has certain argument-naming behavior which can be exploited. Specifically, for parameters with multiple possible names, it will prefer non-dashed to dashed names, and as secondary preference will prioritize longer names over shorter names.
URL: http://click.pocoo.org/dev/parameters/#parameter-names
So if you declare your option as...
#click.option('--format', 'not-format', type=click.Choice(['t', 'j']))
...then Click will prioritize non-dashed variant ('not-format') and call your function with not_format=... argument.
Of course it also means that this alternative spelling can also be used in command line. If that is not desired, then I guess you could add a decorator to rename keyword arguments:
import functools
def rename_kwargs(**replacements):
def actual_decorator(func):
#functools.wraps(func)
def decorated_func(*args, **kwargs):
for internal_arg, external_arg in replacements.iteritems():
if external_arg in kwargs:
kwargs[internal_arg] = kwargs.pop(external_arg)
return func(*args, **kwargs)
return decorated_func
return actual_decorator
Testing code:
if __name__ == '__main__':
#rename_kwargs(different_arg='format')
def tester(different_arg):
print different_arg
tester(format='test value')
Test output:
$ python test_decor.py
test value
In your case, it would look like:
#click.option('--format', type=click.Choice(['t', 'j']))
#replace_kwargs(not_format='format')
def plug(not_format):
pass
Renaming an option to a differently named function argument is possible by decorating the function with
#click.option('--format', '-f', 'format_arg_name')
def plug(format_arg_name):
print(format_arg_name)
then it will remap the option named format and make it available as the format_arg_name parameter.
format_arg_name will not be available as a command line option, but --format and -f are.
I use click like this:
import click
#click.command(name='foo')
#click.option('--bar', required=True)
def do_something(bar):
print(bar)
So the name of the option and the name of the function parameter is the same. When it is not the same (e.g. when you want --id instead of --bar), I get:
TypeError: do_something() got an unexpected keyword argument 'id'
I don't want the parameter to be called id because that is a Python function. I don't want the CLI parameter to be called different, because it would be more cumbersome / less intuitive. How can I fix it?
You just need to add a non-option (doesn't start with -) argument in the click.option decorator. Click will use this as the name of the parameter to the function. This allows you to use Python keywords as option names.
Here is an example which uses id_ inside the function:
import click
#click.command(name='foo')
#click.option('--id', 'id_', required=True)
def do_something(id_):
print(id_)
There is an official example here in the --from option.
I'm using Python's optparse to do what it does best, but I can't figure out how to make the option callback trigger on the default argument value if no other is specified via command-line; is this even possible? This would make my code much cleaner.
I can't use argparse unfortunately, as the platform I'm running on has an outdated Python version.
Edit:
To provide more detail, I'm adding an option with a callback and a default value
parser.add_option(
"-f",
"--format",
type = "string",
action = "callback",
callback = format_callback,
default = "a,b,c,d")
The callback function is defined as follows:
def format_callback(option, opt, value, parser):
# some processing
parser.values.V = processed_value
Basically I'm processing the "--format" value and putting the result into the parser. This works fine, when "--format" is specified directly via command-line, but I'd like the callback to be triggered on the default "a,b,c,d" value as well.
It is simply not possible.
The optparse.OptionParser implementation of parse_args starts with:
def parse_args(self, args=None, values=None):
"""
parse_args(args : [string] = sys.argv[1:],
values : Values = None)
-> (values : Values, args : [string])
Parse the command-line options found in 'args' (default:
sys.argv[1:]). Any errors result in a call to 'error()', which
by default prints the usage message to stderr and calls
sys.exit() with an error message. On success returns a pair
(values, args) where 'values' is an Values instance (with all
your option values) and 'args' is the list of arguments left
over after parsing options.
"""
rargs = self._get_args(args)
if values is None:
values = self.get_default_values()
Default values are set before processing any arguments. Actual values then overwrite defaults as options are parsed; the option callbacks are called when a corresponding argument is found.
So callbacks simply cannot be invoked for defaults. The design of the optparse module makes this very hard to change.
You can inject the default when calling parse_args
options, args = parser.parse_args(args=["--option=default"] + sys.argv[1:])
Since flags passed later in the argument list override those passed earlier, this should work. It's possible you may need to modify your callback function to expect this depending on what it is doing.