I just got set up to use pytest with Python 2.6. It has worked well so far with the exception of handling "import" statements: I can't seem to get pytest to respond to imports in the same way that my program does.
My directory structure is as follows:
src/
main.py
util.py
test/
test_util.py
geom/
vector.py
region.py
test/
test_vector.py
test_region.py
To run, I call python main.py
from src/.
In main.py, I import both vector and region with
from geom.region import Region
from geom.vector import Vector
In vector.py, I import region with
from geom.region import Region
These all work fine when I run the code in a standard run. However, when I call "py.test" from src/, it consistently exits with import errors.
Some Problems and My Solution Attempts
My first problem was that, when running "test/test_foo.py", py.test could not "import foo.py" directly. I solved this by using the "imp" tool. In "test_util.py":
import imp
util = imp.load_source("util", "util.py")
This works great for many files. It also seems to imply that when pytest is running "path/test/test_foo.py" to test "path/foo.py", it is based in the directory "path".
However, this fails for "test_vector.py". Pytest can find and import the vector
module, but it cannot locate any of vector
's imports. The following imports (from "vector.py") both fail when using pytest:
from geom.region import *
from region import *
These both give errors of the form
ImportError: No module named [geom.region / region]
I don't know what to do next to solve this problem; my understanding of imports in Python is limited.
What is the proper way to handle imports when using pytest?
Edit: Extremely Hacky Solution
In vector.py
, I changed the import statement from
from geom.region import Region
to simply
from region import Region
This makes the import relative to the directory of "vector.py".
Next, in "test/test_vector.py", I add the directory of "vector.py" to the path as follows:
import sys, os
sys.path.append(os.path.realpath(os.path.dirname(__file__)+"/.."))
This enables Python to find "../region.py" from "geom/test/test_vector.py".
This works, but it seems extremely problematic because I am adding a ton of new directories to the path. What I'm looking for is either
1) An import strategy that is compatible with pytest, or
2) An option in pytest that makes it compatible with my import strategy
So I am leaving this question open for answers of these kinds.
I was wondering what to do about this problem too. After reading this post, and playing around a bit, I figured out an elegant solution. I created a file called "test_setup.py" and put the following code in it:
I put this file in the top-level directory (such as src). When
pytest
is run from the top-level directory, it will run all test files including this one since the file is prefixed with "test". There are no tests in the file, but it is still run since it begins with "test".The code will append the current directory name of the test_setup.py file to the system path within the test environment. This will be done only once, so there are not a bunch of things added to the path.
Then, from within any test function, you can import modules relative to that top-level folder (such as
import geom.region
) and it knows where to find it since the src directory was added to the path.If you want to run a single test file (such as test_util.py) instead of all the files, you would use:
This runs both the test_setup and test_util code so that the test_setup code can still be used.
import looks in the following directories to find a module:
sys.path is the result of combining the home directory, PYTHONPATH and the standard libraries directory. What you are doing, modifying sys.path is correct. It is something I do regularly. You could try using PYTHONPATH if you don't like messing with sys.path
The issue here is that Pytest walks the filesystem to discover files that contain tests, but then needs to generate a module name that will cause
import
to load that file. (Remember, files are not modules.)Pytest comes up with this test package name by finding the first directory at or above the level of the file that does not include an
__init__.py
file and declaring that the "basedir" for the module tree containing a module generated from this file. It then adds the basedir to the front ofsys.path
and imports using the module name that will find that file relative to the basedir.There are some implications of this of which you should beware:
The basepath may not match your intended basepath in which case the module will have a name that doesn't match what you would normally use. E.g., what you think of as
geom.test.test_vector
will actually be named justtest_vector
during the Pytest run because it found no__init__.py
insrc/geom/test/
and so added that to the path.You may run into module naming collisions if two files in different directories have the same name. For example, lacking
__init__.py
files anywhere, addinggeom/test/test_util.py
will conflict withtest/test_util.py
because both are loaded asimport test_util.py
, with bothtest/
andgeom/test/
in the path.The system you're using here, without explicit
__init__.py
modules, is having Python create implicit namespace packages for your directories. (A package is a module with submodules.) Ideally we'd configure Pytest with a path from which it would also generate this, but it doesn't seem to know how to do that.The easiest solution here is simply to add empty
__init__.py
files to all of the subdirectories undersrc/
; this will cause Pytest to import everything using package/module names that start with directory names undersrc/
.