I cannot find how to add named command line parameter to pytest command, so I can execute tests with custom parameter available as a fixture.
pytest --my-parameter
def test_something(my_parameter):
...
In order to accomplish such behaviour the pytest_addoption shall be defined and new session level fixture in combination with pytestconfig fixture shall be used
# conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption(
"--my-parameter", action="store", default=None,
help=("Parameter description")
)
#pytest.fixture(scope='session')
def my_parameter(pytestconfig):
""" Description of changes triggered by parameter. """
param = pytestconfig.getoption("--my-parameter")
if param is None:
assert False, '--my-parameter parameter must be supplied in order to run test suite.'
return param
# tests.py:
import pytest
def test_something(my_parameter):
...
Related
I am using PyTest framework for writing and running my tests.
I have implemented a concrete logger:
class Logger(object):
class LogFormats:
...
def __init__(self, testname ,setup ,silent=True):
"""
creating concrete logger for pytest.
the logger will create a file for the test in specific test directory in quali FS and will
write to this file all test log output (colored).
:param: testname: test name - recieved from pytest fixtures (command line parameters)
:param: setup: test setup - recieved from pytest fixtures (command line parameters)
:param: silent: log test in silent mode (info only) or not silent mode (everything is logged)
:param: root_password: password for root user
"""
....
...
and in the conftest.py file I wrote the function that will be invoked when this logger will be requested (creating a logger fixture)
#pytest.fixture(scope="module",autouse=True)
def logger(request):
setup = request.config.getoption('--setupname')
logger = Logger(testname=request.node.name, setup=setup)
return logger
Now, my question is how do I make this concrete logger global using pytest?
Meaning I don't want to pass it as argument to the test function like this:
def test_logger(other_fixture,logger):
but still be able to use it inside the test_logger test function (like global variable)
You could do
#pytest.mark.usefixtures("logger")
def test_logger(other_fixture):
I have the following as conftest.py -->
def pytest_addoption(parser):
parser.addoption("--browser")
parser.addoption("--osType", help="Type of operating system")
parser.addoption("--hostURL", action="store", help="prod, stage, or dev")
#pytest.fixture(scope="session")
def browser(request):
return request.config.getoption("--browser")
#pytest.fixture(scope="session")
def osType(request):
return request.config.getoption("--osType")
#pytest.fixture(autouse=True)
def hostURL(request):
return request.config.getoption("--hostURL")
I would like to use the --hostURL flag to pass in value such as prod, stage or dev.
Here's how my test_TheMainTest.py looks -->
import unitest
import pytest
class CheckStatusCodeTest(unittest.TestCase, LoginPage, CustomSeleniumDriver):
def test_CheckStatusCodeOfPages(self, hostURL):
self.login(hostURL)
When I run the above test using pytest -q -s --hostURL prod I get the following error -->
TypeError: test_CheckStatusCodeOfCRPages() missing 1 required positional argument: 'hostURL'
Quoting the docs:
Note
unittest.TestCase methods cannot directly receive fixture arguments as implementing that is likely to inflict on the ability to run general unittest.TestCase test suites.
However, you can still pass regular fixture values to unittest-style tests using autouse fixtures:
class CheckStatusCodeTest(unittest.TestCase):
#pytest.fixture(autouse=True)
def _pass_fixture_value(self, hostURL):
self._hostURL = hostURL
def test_CheckStatusCodeOfPages(self):
assert self._hostURL
You can also check out this answer of mine tackling the same issue for more examples.
Another possibility is to implement an autouse fixture that modifies the test class explicitly. This is useful if you have lots of test classes that should have the identical setup:
#pytest.fixture(scope="class")
def set_fixture_value_to_class(request, hostURL):
request.cls._hostURL = hostURL
#pytest.mark.usefixtures('set_fixture_value_to_class')
class CheckStatusCodeTest(unittest.TestCase):
def test_CheckStatusCodeOfPages(self):
assert self._hostURL
#pytest.mark.usefixtures('set_fixture_value_to_class')
class AnotherTest(unittest.TestCase):
def test_spam(self):
assert self._hostURL
In this case, no need to copy the same fixture to each test class. Just mark all relevant test classes and you're good to go.
I need to setup tests depending on where or how I want to run py.test tests. I want to be able to do something like this
py.test --do_this
py.test --do_that
and retrieve the values in the setup method of a test class
class TestSuite(object):
def setup(self):
if (do_this):
...
Something like that. Can this be done? And if so, how?
From the documentation, you can add arguments to the pytest caller
# content of test_sample.py
def test_answer(do):
if do == "this":
print ("do this option")
elif do == "that":
print ("do that option")
assert 0 # to see what was printed
# content of conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption("--do", action="store", default="that",
help="my option: this or that")
#pytest.fixture
def do(request):
return request.config.getoption("--do")
Then, you can call pytest as pytest -q test_sample.py to get the default case with that and pytest -q --do=this for the other case
I am calling a test file made in python, with the following:
py.test mytest.py
I did check the documentation and followed the example to add a fixture that read the argument passed, into a conftest file:
import pytest
def pytest_addoption(parser):
parser.addoption("--myoption", action="store", default="",
help="Specify the option string")
#pytest.fixture
def myoption(request):
return request.config.getoption("--myoption")
And in my unit test class, I did add the decorator to call the fixture:
import pytest
#pytest.mark.usefixtures("myoption")
class VariousTests(unittest.TestCase):
def test_runsomething(self):
print(myoption)
Although I can't get the value of my option in any way; what am I doing wrong here? All that I want is to pass a string to pytest, so it can populate a class variable in my unit test class.
If you use pytest.mark.usefixtures marker, you can't access the fixture object, but only have side effect of the fixture function.
If you want to use fixture object access in the test method, use explicitly specify fixture as a parameter:
import pytest
def test_runsomething(myoption): # <--- myoption specified as parameter
print(myoption)
➜ t cat t.py
import pytest
def test_runsomething(myoption):
print(myoption)
➜ t py.test -s --myoption myoption-value t.py
==========================================
platform darwin -- Python 2.7.13, pytest-3.0.5, py-1.4.31, pluggy-0.4.0
rootdir: /private/tmp/t, inifile:
collected 1 items
t.py myoption-value
.
============ 1 passed in 0.00 seconds ============
If you want to use unittest.TestCase based tests, you need to modify conftest.py so that fixture is injected into the class:
conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption("--myoption", action="store", default="",
help="Specify the option string")
#pytest.fixture
def myoption(request):
request.cls.myoption = request.config.getoption("--myoption")
# ^-- set `myoption` attribute
and access it in the test method as self.myoption or VariousTests.myoption:
import unittest
import pytest
#pytest.mark.usefixtures("myoption")
class VariousTests(unittest.TestCase):
def test_runsomething(self):
print(self.myoption) # <----
I use a simple command to run my tests with nose:
#manager.command
def test():
"""Run unit tests."""
import nose
nose.main(argv=[''])
However, nose supports many useful arguments that now I could not pass.
Is there a way to run nose with manager command (similar to the call above) and still be able to pass arguments to nose? For example:
python manage.py test --nose-arg1 --nose-arg2
Right now I'd get an error from Manager that --nose-arg1 --nose-arg2 are not recognised as valid arguments. I want to pass those args as nose.main(argv= <<< args comming after python manage.py test ... >>>)
Flask-Script has an undocumented capture_all_flags flag, which will pass remaining args to the Command.run method. This is demonstrated in the tests.
#manager.add_command
class NoseCommand(Command):
name = 'test'
capture_all_args = True
def run(self, remaining):
nose.main(argv=remaining)
python manage.py test --nose-arg1 --nose-arg2
# will call nose.main(argv=['--nose-arg1', '--nose-arg2'])
In the sources of flask_script you can see that the "too many arguments" error is prevented when the executed Command has the attribute capture_all_args set to True which isn't documented anywhere.
You can set that attribute on the class just before you run the manager
if __name__ == "__main__":
from flask.ext.script import Command
Command.capture_all_args = True
manager.run()
Like this additional arguments to the manager are always accepted.
The downside of this quick fix is that you cannot register options or arguments to the commands the normal way anymore.
If you still need that feature you could subclass the Manager and override the command decorator like this
class MyManager(Manager):
def command(self, capture_all=False):
def decorator(func):
command = Command(func)
command.capture_all_args = capture_all
self.add_command(func.__name__, command)
return func
return decorator
Then you can use the command decorator like this
#manager.command(True) # capture all arguments
def use_all(*args):
print("args:", args[0])
#manager.command() # normal way of registering arguments
def normal(name):
print("name", name)
Note that for some reason flask_script requires use_all to accept a varargs but will store the list of arguments in args[0] which is a bit strange. def use_all(args): does not work and fails with TypeError "got multiple values for argument 'args'"
Ran into an issue with davidism's soln where only some of the args were being received
Looking through the docs a bit more it is documented that nose.main automatically picks up the stdin
http://nose.readthedocs.io/en/latest/api/core.html
So we are now just using:
#manager.add_command
class NoseCommand(Command):
name = 'nose'
capture_all_args = True
def run(self, remaining):
nose.main()