22

I'm using python pytest to run my unit tests. My project folders are:

Main - contains data file: A.txt

Main\Tests - the folder from which I run pytest

Main\Tests\A_test - folder that contains a test file

The test in A_test folder uses the file A.txt (that is in Main folder).

My problem is that when I run py.test the test fails because it can't find A.txt.

I found out that it is because pytest uses the path Main\Test when running the test instead of changing the path to Main\Tests\A_test (I'm using relative path when opening A.txt inside the test file)

My question: is there a way to make pytest change directory to the folder of the test it executes for each test? so that relative paths inside the tests will still work?

Is there some other generic way to solve it? (I don't want to change everything to absolute paths or something like this, also this is an example, in real life I have several hundreds tests).

Thank you,

Noam

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Noam
  • 1,640
  • 4
  • 26
  • 55

5 Answers5

14

Option A — Minimal solution

At the root of your project, create a file called tests.py with the following in it

import os, pathlib
import pytest

os.chdir( pathlib.Path.cwd() / 'Tests' )

pytest.main()

You can then use the command python tests.py to run the tests.




Option B — With batch/bash test runners

For those who prefer using batch/bash to run scripts, we can change directories within batch/bash, and then call a Python script that runs the pytest framework. To do this, create the following scripts in the project folder.

test.bat (for Windows)

@echo off

cd /d %~dp0Tests
python %~dp0Tests/runner.py %*
cd /d %~dp0

test.sh (for Linux)

cd $PWD/Tests
python runner.py $@
cd $PWD

And then in the Tests folder, create a file called runner.py with the following

import pathlib, sys
import pytest

cwd = pathlib.Path.cwd()

# Add the project's root directory to the system path
sys.path.append(str( cwd.parent ))

# This is optional, but you can add a lib directory 
# To the system path for tests to be able to use
sys.path.append(str( cwd / 'lib' ))

pytest.main()

If your directory structure includes some type of lib folder within your Tests folder, we can instruct pytest to ignore it by creating a pytest.ini config file with the following.

[pytest]
norecursedirs = lib

Under this scenario, your directory/file structure would end up being:

root
├── test.bat
├── test.sh
├── Main
└── Tests
    ├── runner.py
    ├── pytest.ini # Optional pytest config file
    ├── lib # Optional, contains helper modules for the tests
    ├── tests # Tests go here
    └── # Or, in the OPs case, you could also place all of your tests here




Additional Comments

The methods above aren't the typical way of running pytest, but I prefer using pytest.main() because it allows us to:

  • Have any directory structure.
  • Execute code before the test runner starts.
  • And you can still pass in command line options, and it will behave exactly the same as if you were running the pytest command directly.
hostingutilities.com
  • 8,894
  • 3
  • 41
  • 51
  • The issue I have with this solution is I am not sure how to leverage this from using PyCharm as my test runner. I found the approved solution with less upvotes to be better for PyCharm. – Dan Ciborowski - MSFT Jan 13 '20 at 21:50
  • how you can tell pytest.main() where is the folder with the tests? – pelos Mar 10 '22 at 16:01
  • Pytest will automatically look inside of the current directory for tests. With option A I changed the current directory with `os.chdir( pathlib.Path.cwd() / 'Tests' )` and with Option B it was changed with `cd $PWD/Tests` or `cd /d %~dp0Tests`. – hostingutilities.com Mar 10 '22 at 20:34
5

Well I kind of solved it, not sure it is the best way but it is working:

In each of the tests:

  1. I check if the test is being executed from it directory or from \Main\Tests
  2. If it is being executed from \Main\Tests then I chdir to \Main\Tests\A_test

I do this under the def setUpClass method.

For example:

@classmethod
def setUpClass(cls):
    if (os.path.exists(os.path.join(os.curdir, "A_test"))):
        os.chdir("A_test")

This makes the test pass no matter if it is executed from Tests folder (with pytest) or from A_test folder (through pycharm)

Noam
  • 1,640
  • 4
  • 26
  • 55
  • 3
    Since you came up with the solution I don't want feel right making a new answer. But you can also add the code from your `setUpClass` method to the `A_test/__init__.py` file. This way if you have more then one test in your directory, you do not need to add the code to every file. This is PyCharm friendly – Dan Ciborowski - MSFT Jan 13 '20 at 21:47
5

Adding __init__.py to the package of the tests worked for me. All test are executed afterwards.

shalama
  • 1,133
  • 1
  • 11
  • 25
  • 2
    Adding `__init__.py` in test dir makes pytest add them to the pythonpath, but it means if you have a directory called "os" for example, it will clash with the python package os, and wreak havoc. It happens often, I do not recommend. – Pierre.Sassoulas Oct 01 '20 at 08:31
4

Assuming you need your root Main in the sys.path.

Giving your current dir is Main/:

$python -m pytest Tests/A_test

This will append Main to the sys.path and run tests in the A_test subdirectory. More about pythonpath and pytest relationship here: http://doc.pytest.org/en/latest/pythonpath.html#pythonpath

klapshin
  • 761
  • 8
  • 14
1

A "generic" (i.e. often-used) way to solve this is to install your package as an editable install:

> py -m pip -e .

This requires first adding a __init__.py in your folder (to turn it into a package) before running the above command.

Aside from this, I think the "truest" answer (perhaps subject to opinion) comes from the pytest docs themselves: the testpaths configuration option can be set in a setup.cfg, pytest.ini, tox.ini, or pyroject.toml file.

adam.hendry
  • 4,458
  • 5
  • 24
  • 51
  • how? I've added testpaths = nested/dir/tests and still doesnt work – Mesco Sep 28 '22 at 15:04
  • @Mesco Which configuration file format are you using? `setup.cfg`, `pytest.ini`, `tox.ini`, or `pyproject.toml`? If you share a gist, I can take a look. – adam.hendry Sep 30 '22 at 00:31
  • pytest.ini. My problem is about tests discovery, not about including the path during test execution (I just install package with -e). I can run tests with `pytest Subfolder/tests`. But can't setup to run just with `pytest`. The reason is that I want to attach VSCode debugger to tests -- and there is no option to add positional argument to test command (I can only add additional arguments that are appended after `testrunner.py -- `). – Mesco Oct 03 '22 at 06:48
  • To debug with pytest, I wrote a `launch.json` in my `.vscode` folder like in [this answer](https://stackoverflow.com/questions/62419998/how-can-i-get-pytest-to-not-catch-exceptions/62563106#62563106), but if you use `pytest-cov`, [VSCode won't stop at breakpoints](https://code.visualstudio.com/docs/python/testing#_pytest-configuration-settings), so you have to also add `"env": {"PYTEST_ADDOPTS": "--no-cov"}`. – adam.hendry Oct 03 '22 at 07:27