Richard Foo
Richard Foo

Reputation: 195

Get pytest to look within the base directory of the testing script

In pytest, my testing script compares the calculated results with baseline results which are loaded via

SCRIPTLOC = os.path.dirname(__file__)
TESTBASELINE = os.path.join(SCRIPTLOC, 'baseline', 'baseline.csv')
baseline = pandas.DataFrame.from_csv(TESTBASELINE)

Is there a non-boilerplate way to tell pytest to start looking from the root directory of the script rather than get the absolute location through SCRIPTLOC?

Upvotes: 4

Views: 9040

Answers (1)

systemexit
systemexit

Reputation: 590

If you are simply looking for the pytest equivalent of using __file__, you can add the request fixture to your test and use request.path

From the docs:

class FixtureRequest
  ...
  property path: Path
      Path where the test function was collected.

So an example might look like:

def test_script_loc(request):
    baseline = request.path.parent.joinpath('baseline', 'baseline.cvs')
    print(baseline)

If you're wanting to avoid boilerplate though, you're not going to gain much from doing this (assuming I understand what you mean by 'non-boilerplate')

Personally, I think using the fixture is more explicit (within pytest idioms), but I prefer to wrap the request manipulation in another fixture so I know that I'm explicitly grabbing sample test data just by looking at the method signature of a test.


Here's a snippet I use (modified to match your question, I use a subdirectory hierarchy):

# in conftest.py
import pytest

from pathlib import Path


@pytest.fixture(scope="module")
def script_loc(request):
    '''Return the directory of the currently running test script'''

    return request.path.parent

And sample usage

def test_script_loc(script_loc):
    baseline = script_loc.joinpath('baseline/baseline.cvs')
    print(baseline)

pytest <= 6 compatibility

I've long since stopped supporting legacy Python and instead use pathlib.
Given that, I no longer return LocalPath objects nor depend on their API, but occassionally I still need to support platforms with pytest<=6.0 which do not yet have the request.path property.
The pytest team is also planning to eventually deprecate py.path and is porting their internals to the standard library pathlib. This started as early as pytest 3.9.0 with the introduction of tmp_path, though the actual removal of LocalPath attributes may not happen for some time.

Until you can upgrade to pytest>=7.0, it's easy enough to convert the LocalPath to a Path ourselves.

Here's a variant of the above example using a Path object, tweak as it suits you:

# in conftest.py
import pytest

from pathlib import Path


@pytest.fixture(scope="module")
def script_loc(request):
    '''Return the directory of the currently running test script'''

    return Path(request.fspath).parent 

And sample usage

def test_script_loc(script_loc):
    baseline = script_loc.joinpath('baseline/baseline.cvs')
    print(baseline)

Legacy py.path

For older versions of pytest that depend on py.path and/or required Python 2.x support, the original version of this answer used the request.fspath attribute to return an instance of py.path.LocalPath.

From the docs:

class FixtureRequest
  ...
  fspath
      the file system path of the test module which collected this test.

So an example might look like:

def test_script_loc(request):
    baseline = os.path.join(request.fspath.dirname, 'baseline', 'baseline.cvs')
    print(baseline)

The fixture variant might appear as follows:

# in conftest.py
import pytest

@pytest.fixture(scope="module")
def script_loc(request):
    '''Return the directory of the currently running test script'''

    # uses .join instead of .dirname so we get a LocalPath object instead of
    # a string. LocalPath.join calls normpath for us when joining the path
    return request.fspath.join('..') 

And sample usage:

def test_script_loc(script_loc):
    baseline = script_loc.join('baseline/baseline.cvs')
    print(baseline)

Upvotes: 14

Related Questions