Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using pytest_addoptions in a non-root conftest.py

I have a project that has the following structure:

Project/
|
+-- src/
|   |
|   +-- proj/
|       |
|       +-- __init__.py
|       +-- code.py
|       +-- tests/
|           |
|           +-- __init__.py
|           +-- conftest.py
|           +-- test_code.py
+-- doc/
+-- pytest.ini
+-- setup.py

The importable package proj lives under the src directory. Tests are installed along with it, in the proj.tests sub-package. The code, the tests themselves, and the installation all work fine. However, I have trouble passing options to the tests.

I have an option, --plots, that is defined in conftest.py (under Project/src/proj/tests/), which creates a .plots/ folder in the root folder and places some graphical debugging there:

# conftest.py
def pytest_addoption(parser):
    parser.addoption("--plots", action="store_true", default=False)

There are two different ways I'd like to have to run this test:

  1. The first is from the command line, in the Project directory:

     $ pytest --plots
    

    This fails immediately with

     usage: pytest [options] [file_or_dir] [file_or_dir] [...]
     pytest: error: unrecognized arguments: --plots
       inifile: /.../Project/pytest.ini
       rootdir: /.../Project
    

    If I add the package directory, the run goes fine:

     $ pytest src/proj --plots
     ...
     rootdir: /.../Project, inifile: pytest.ini
     ...
    

    It also goes well if I specify the tests sub-directory:

     $ pytest src/proj/tests --plots
     ...
     rootdir: /.../Project, inifile: pytest.ini
     ...
    
  2. The second way is to run the test function of the package itself. The test function is defined in Project/src/proj/__init__.py like this:

     # __init__.py
    
     def test(*args, **kwargs):
         from pytest import main
         cmd = ['--pyargs', 'proj.tests']
         cmd.extend(args)
         return main(cmd, **kwargs)
    

    The function is intended to be run after Project is installed like this:

     >>> import proj
     >>> proj.test()
    

    Generally, this works OK, but if I do

     >>> proj.test('--plots')
    

    I get the same error as usual:

     usage:  [options] [file_or_dir] [file_or_dir] [...]
     : error: unrecognized arguments: --plots
       inifile: None
       rootdir: /some/other/folder
    

    For this test, I ran python setup.py develop and then cd'dto/some/other/folder` to make sure everything installed correctly.

My goal is to have both options working correctly when I pass in the --plots command line option. It seems that I have found a workaround for option #1, which is to manually pass in one of the packages to pytest, but I don't even fully understand how that works (why can I pass in either src/proj or src/proj/tests?).

So the question is, how to I get pytest to consistently run my test package so that the correct conftest.py gets picked up? I am willing to consider just about any reasonable alternative that allows me to get the --plots option working consistently in both the source (running in a shell from Project) version and the proj.test() version.


For reference, here is my pytest.ini file:

# pytest.ini

[pytest]
testpaths = src/proj/tests
confcutdir = src/proj/tests

It doesn't appear to make any difference.

I am running with pytest 3.8.0, on Pythons 2.7, 3.3, 3.4, 3.5, 3.6 and 3.7 (in anaconda). In all versions, the results are reproducible.

like image 400
Mad Physicist Avatar asked Sep 21 '18 16:09

Mad Physicist


People also ask

Where should Conftest py be located?

You can put fixtures into individual test files, but to share fixtures among multiple test files, you need to use a conftest.py file somewhere centrally located for all of the tests. For the Tasks project, all of the fixtures will be in tasks_proj/tests/conftest.py.

Can we have multiple Conftest py?

You can have multiple nested directories/packages containing your tests, and each directory can have its own conftest.py with its own fixtures, adding on to the ones provided by the conftest.py files in parent directories.

Do I need to import Conftest?

The conftest.py file serves as a means of providing fixtures for an entire directory. Fixtures defined in a conftest.py can be used by any test in that package without needing to import them (pytest will automatically discover them).

How does Conftest work in pytest?

conftest.py is where you setup test configurations and store the testcases that are used by test functions. The configurations and the testcases are called fixture in pytest. The test_*. py files are where the actual test functions reside.


1 Answers

Quote from the pytest_addoption docs:

Note: This function should be implemented only in plugins or conftest.py files situated at the tests root directory due to how pytest discovers plugins during startup.

...

Note that pytest does not find conftest.py files in deeper nested sub directories at tool startup. It is usually a good idea to keep your conftest.py file in the top level test or project root directory.

When you call pytest from the Project dir, pytest recognizes it as the root dir, so the addoption hook in Project/src/proj/tests/conftest.py is ignored.

If the project structure you described is a requirement, I'd move the pytest_addopts hook impl to a separate plugin module; this can reside anywhere in the project dir, for example Project/src/proj/tests/plugin_plots.py. Now, if you call

$ pytest -p src.proj.tests.plugin_plots

the addoption hook impl will be executed and the args will be added correctly.

(you may need to adjust the sys.path to be able to load the custom plugin module correctly, e.g. use PYTHONPATH=. pytest -p or python -m pytest -p, or install the project in editable mode etc)

To not to enter the -p arg each time, persist it in pytest.ini, e.g.

# pytest.ini
[pytest]
addopts=-p src.proj.tests.plugin_plots

Edit:

When running the tests programmatically, ehnance the args list:

def test(*args, **kwargs):
    from pytest import main
    cmd = ['-p', 'proj.tests.plugin_plots', '--pyargs', 'proj.tests']
    cmd.extend(args)
    return main(cmd, **kwargs)

should work just fine.

like image 168
hoefling Avatar answered Sep 26 '22 21:09

hoefling