I would like to access command line arguments passed to pytest from within a non-test class.
I have added the following to my conftest.py file
def pytest_addoption(parser): # pragma: no cover
"""Pytest hook to add custom command line option(s)."""
group = parser.getgroup("Test", "My test option")
group.addoption(
"--stack",
help="stack", metavar="stack", dest='stack', default=None)
But I can not work out how to access the value passed on the command line. I have found code on how to access it from a fixture, but I would like to access it from a method or class that is not part of the test case.
You can access the parameters with sys.argv.
It will return a list of all the arguemnts you sent wrote when you called using the command line.
For example
def pytest_addoption(parser): # pragma: no cover
"""Pytest hook to add custom command line option(s)."""
params = sys.argv[1:]
group = parser.getgroup("Test", "My test option")
group.addoption(
"--stack",
help="stack", metavar="stack", dest='stack', default=None)
Yes you could add one more pytest hook pytest_configure to your conftest.py as follows:
stack = None
def pytest_configure(config):
global stack
stack = config.getoption('--stack')
Now your argument stack is available at global level.
Related
We have unit tests running via Pytest, which use a custom decorator to start up a context-managed mock echo server before each test, and provide its address to the test as an extra parameter. This works on Python 2.
However, if we try to run them on Python 3, then Pytest complains that it can't find a fixture matching the name of the extra parameter, and the tests fail.
Our tests look similar to this:
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url):
res_id = self._test_resource(url)['id']
result = update_resource(None, res_id)
assert not result, result
self.assert_archival_error('Server reported status error: 404 Not Found', res_id)
With a decorator function like this:
from functools import wraps
def with_mock_url(url=''):
"""
Start a MockEchoTestServer and call the decorated function with the server's address prepended to ``url``.
"""
def decorator(func):
#wraps(func)
def decorated(*args, **kwargs):
with MockEchoTestServer().serve() as serveraddr:
return func(*(args + ('%s/%s' % (serveraddr, url),)), **kwargs)
return decorated
return decorator
On Python 2 this works; the mock server starts, the test gets a URL similar to "http://localhost:1234/?status=404&content=test&content-type=csv", and then the mock is shut down afterward.
On Python 3, however, we get an error, "fixture 'url' not found".
Is there perhaps a way to tell Python, "This parameter is supplied from elsewhere and doesn't need a fixture"? Or is there, perhaps, an easy way to turn this into a fixture?
You can use url as args parameter
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, *url):
url[0] # the test url
Looks like Pytest is content to ignore it if I add a default value for the injected parameter, to make it non-mandatory:
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url=None):
The decorator can then inject the value as intended.
consider separating the address from the service of the url. Using marks and changing fixture behavior based on the presence of said marks is clear enough. Mock should not really involve any communication, but if you must start some service, then make it separate from
with_mock_url = pytest.mark.mock_url('http://www.darknet.go')
#pytest.fixture
def url(request):
marker = request.get_closest_marker('mock_url')
if marker:
earl = marker.args[0] if args else marker.kwargs['fake']
if earl:
return earl
try:
#
earl = request.param
except AttributeError:
earl = None
return earl
#fixture
def server(request):
marker = request.get_closest_marker('mock_url')
if marker:
# start fake_server
#with_mock_url
def test_resolve(url, server):
server.request(url)
Imagine a toy test definition:
#pytest.mark.parametrize("bar", [1, 2, 3])
def test_foo(bar):
assert some_result(bar)
I now want to be able to provide bar on the command line. As far as I know this is not built into Py.Test yet:
pytest --param "bar=7"
And write my test parameter definition like this:
#pytest.mark.parametrize(*from_args_if_available("bar", [1, 2, 3]))
def test_foo(bar):
assert some_result(bar)
I can implement from_args_if_available() parsing all command line arguments again and looking for --params of course. Another way would be to make request.config available globally (which would be ugly).
I wonder if it's possible to access the request.config element from anywhere in order to have all command line option definition at one place (and thus the help).
So is there another way to access the parsed command line options which I can use in the described way?
I'll post my currently working solution here:
in a file utils.py I define a function param:
def param(name, default_value):
parser = argparse.ArgumentParser()
parser.add_argument("--param", action="store")
args, unknown = parser.parse_known_args()
try:
if args.param:
for p_name, p_value in (param.split("=") for param in args.param.split(";")):
if p_name == name:
return p_name, ast.literal_eval(p_value)
except (SyntaxError, ValueError):
raise RuntimeError(
"Wrong parameter definition: %r, must be 'name1=<expr>;name2=<expr>;..'"
% args.param)
return name, default_value
Which can be used like this:
import pytest
from util import param
#pytest.mark.parametrize(*param("bar", [1, 2, 3]))
def test_foo(bar, env_type):
print("param: ", "bar", bar)
assert bar < 7
A pytest call can look like this now:
pytest --param "bar=[5,6,7];unused=range(5)"
in conftest.py I now have to add the param argument as well - otherwise an exception gets raised when I define parameters on command line:
def pytest_addoption(parser):
parser.addoption("--param", action="store", help="overwrite test parameters")
Upside: it works :)
Downside: It's an ugly workaround, param() can't be defined inside conftest.py
Say I have this test in tests.py
def test_user(username='defaultuser'):
Case 1
I want to pass the username to test from the command line, something like
$ pytest tests.py::test_user user1 # could be --username=user1
How do I do that?
Case 2
I want to pass a list of usernames to test, like
$ pytest tests.py::test_user "user1, user2, user3"
I want to achieve something like
#pytest.mark.parametrize("username", tokenize_and_validate(external_param))
def test_user(username):
pass
def tokenize_and_validate(val):
if not val:
return 'defaultuser'
return val.split(',')
How can I do that?
Thank you
When you pass a parameter from the command line at first you need to create a generator method to get the value from the command line this method run every test.
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])
Then you can run from the command line with a command line argument:
pytest -s tests/my_test_module.py --name abc
Follow the link for more details
To mock the data, you can use fixtures or use builtin unittest mocks.
from unittest import mock
#mock.patch(func_to_mock, side_effect=func_to_replace)
def test_sth(*args):
pass
Command line options are also available.
In the following example, how do I pass args of run_tests() to pytest.main(...) so that I can use args for the test methods of TestFooBar in test_module.py?
my_module.py
def run_tests(args):
# How do I pass parameter 'args' to pytest here.
pytest.main(['-q', '-s', 'test_module.py::TestFooBar'])
test_module.py
class TestFooBar():
# How do I get 'args' of 'run_tests()' from 'my_module.py' here.
#pytest.mark.parametrize("args", [args])
def test_something(args):
assert 'foo' == args['foo']
#pytest.mark.parametrize("args", [args])
def test_something_else(args):
assert 'bar' == args['bar']
If you execute pytest.main the you are doing the equivalent to calling py.test from the command line, so the only way to pass arguments that I am aware of is via a command line parameter. For this to be possible, your parameters need to be convertable to and from string.
Basically this means creating a conftest.py with the following
def pytest_addoption(parser):
parser.addoption('--additional_arguments', action='store', help='some helptext')
def pytest_configure(config):
args = config.getoption('additional_arguments')
Now do something with args: deserialize it, make it a global variable, make it a fixture, anything you want. Fixtures from conftest.py will be available to the entire test.
Needless to say that your call should now include the new parameter:
pytest.main(['-q', '-s', '--additional_arguments', args_string, 'test_module.py::TestFooBar'])
is it possible to use pytest_addoption(parser) to create a list that is used by pytest.yield_fixture? i.e.
def pytest_addoption(parser):
parser.addoption("-foo", action="store",defaults="1,2,3")
#pytest.yield_fixture(params=request.config.getoption('--foo').split(','))
def test_bar(request):
do_something(request.param)
say you had 6 browsers, and you wanted to ability to run the tests against 1 browser as a quick check. I can't figure out how to get in place before test discovery/generation. Help
This obviously doesn't work since you the request variable does not exist in the global module scope, which is when the expression in the decorator is executed. The way to solve this is use the pytest_generate_tests hook:
# conftest.py
def pytest_addoption(parser):
parser.addoption('--foo', action='store', defaults='1,2,3')
def pytest_configure(config):
config.foo = config.getoption('foo').split(',')
def pytest_generate_tests(metafunc):
if 'foo' in metafunc.fixturenames:
metafunc.parametrize('foo', metafunc.config.foo)
# test_bar.py
def test_bar(foo):
do_something(foo)