Reputation: 4716
I have a file structure like this:
./
project_name/
__init__.py
setup.py
tests/
.travis.yml
So I run py.test in my .travis.yml. Depending on how I install the package, it either works or I encounter an error.
If I install the package with pip install -e .
, it's all fine, but this has the disadvantage of not being a realistic install scenario.
If I install the package with pip install .
, it is installed as it would be on another machine in non-dev mode. However, a problem arises: When I run pytest, my tests use import project_name
. Then, python imports locally from the directory instead of using the installed package, with leads to an ImportMismatchError
.
What would be the best way to deal with this?
Upvotes: 2
Views: 244
Reputation: 66501
If you want to run the tests on installed code, you need an intermediary directory (not package) containing all your source code that will be installed; the common name for that being simply src
. Sample layout:
project_root
├── src
│ ├── spam
│ │ ├── __init__.py
│ │ └── eggs.py
│ └── ...
├── tests
│ ├── test_spam.py
│ └── ...
└── setup.py
Adjusting the setup script to honor the src
dir:
# setup.py
from setuptools import setup, find_packages
setup(
name='spam',
...
packages=find_packages('src'),
package_dir={'': 'src'},
...
)
With this layout, although the current dir (project_root
) is still added to sys.path
, all the packages are hidden from importing. This way, you are forced to install your package to be able to invoke the tests and are always testing the installed code. Usually, you install the package in development mode via pip install --editable .
on your local machine while writing code, and install the package via pip install .
on CI server and the tests will be executed over an actual package installation. Another advantage is that this layout doesn't allow you to import your package's source code in setup script, you don't run into a chicken-egg problem when installing (to install your code, your code must be installed already).
Should you have loose modules, I prefer globbing with pathlib
:
py_modules=[p.name for p in pathlib.Path('src').glob('*.py')]
Should you still need Python 2 compatibility, a solution could look like:
py_modules=[os.path.splitext(os.path.basename(p))[0] for p in glob.glob("src/*.py")]
Upvotes: 1
Reputation: 28
One solution might be to make a file called tox.ini
at the top level to use with tox. This is a tool that takes care of building environments - it's like make
but more specific for python stuff. If you make a tox.ini
:
[tox]
envlist = py
[testenv]
commands = pytest tests
deps =
pytest
then install tox
with pip3 install tox
, then just the command tox
from in the command line, it will take care of installing the package and running the tests in an isolated virtual environment.
Then, you can do this inside your travis.yml
:
language: python
python:
- 3.6
install:
- pip install tox
script:
- tox
And Travis will use tox
to run the tests in a more reproducible way.
Upvotes: 1