@ucals Iβm sorry that the documentation around the test job using pip isnβt very clear. This all very much depends on how you plan on setting up your project structure around tests. Because there are different best practices we canβt assume the project structure will be one way. There also so many ways a project can be setup and the packages added to the PYTHONPATH
. Hereβs a few options, inclduing a quick fix and some suggestions around python packaging.
Quick Fix
Make sure PYTHONPATH
is set. Run export PYTHONPATH=$PWD/src
as the fastest way to get this going (also assuming pytest is defined in a requirements.txt already - I simply have pytest installed via pip install pytest
in my tests below)
circleci@c772bfc9b4c5:~/project$ tree
.
βββ src
β βββ main.py
βββ tests
βββ test_hello.py
2 directories, 2 files
circleci@c772bfc9b4c5:~/project$ cat src/main.py
def hello_world():
print("Hello world")
return True
circleci@c772bfc9b4c5:~/project$ cat tests/test_hello.py
import main
def test_hw():
main.hello_world()
circleci@c772bfc9b4c5:~/project$ echo $PYTHONPATH
circleci@c772bfc9b4c5:~/project$ pytest -q
================================================================== ERRORS ===================================================================
___________________________________________________ ERROR collecting tests/test_hello.py ____________________________________________________
ImportError while importing test module '/home/circleci/project/tests/test_hello.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../.pyenv/versions/3.9.0/lib/python3.9/importlib/__init__.py:127: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_hello.py:3: in <module>
import main
E ModuleNotFoundError: No module named 'main'
========================================================== short test summary info ==========================================================
ERROR tests/test_hello.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.08s
circleci@c772bfc9b4c5:~/project$ export PYTHONPATH=$PWD/src
circleci@c772bfc9b4c5:~/project$ pytest -q
. [100%]
1 passed in 0.01s
I donβt recommend this as a python best practise, but you can get this going by also adding this environment key to your job:
environment:
PYTHONPATH: /home/circleci/project/src
Other solutions around python best practices
Make src
a python package
As a python best practise, src
should be setup as a python package. You can do that by creating an __init__.py
in src, and updating the import in test_hello.py
. Notice here I had to change the python path again, but instead of specifying the src
directory, I can simply specify the project root. All directories with __init__.py
will be discovered as python packages. Now, in test_hello.py
we change the import to be from <package> import <module>
circleci@c772bfc9b4c5:~/project$ tree
.
βββ src
β βββ __init__.py
β βββ main.py
βββ tests
βββ test_hello.py
2 directories, 3 files
circleci@c772bfc9b4c5:~/project$ cat tests/test_hello.py
from src import main
def test_hw():
main.hello_world()
circleci@c772bfc9b4c5:~/project$ echo $PYTHONPATH
/home/circleci/project
circleci@c772bfc9b4c5:~/project$ pytest
============================================================ test session starts ============================================================
platform linux -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/circleci/project
collected 1 item
tests/test_hello.py . [100%]
============================================================= 1 passed in 0.01s =============================================================
If the PYTHONPATH is not set, it will failβ¦
circleci@c772bfc9b4c5:~/project$ echo $PYTHONPATH
circleci@c772bfc9b4c5:~/project$ pytest
============================================================ test session starts ============================================================
platform linux -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/circleci/project
collected 0 items / 1 error
================================================================== ERRORS ===================================================================
___________________________________________________ ERROR collecting tests/test_hello.py ____________________________________________________
ImportError while importing test module '/home/circleci/project/tests/test_hello.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../.pyenv/versions/3.9.0/lib/python3.9/importlib/__init__.py:127: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_hello.py:1: in <module>
from src import main
E ModuleNotFoundError: No module named 'src'
========================================================== short test summary info ==========================================================
ERROR tests/test_hello.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================= 1 error in 0.08s ==============================================================
Avoid setting PYTHONPATH by installing packages
Now, how can we get around this? One way is by turning your project into an installable site package by creating a setup.py
. Here, weβve added a minimal setup.py and then run pip install -e .
to install the package βsrcβ into the path. Note, install_requires=[pytest]
would make sure pytest is installed.
circleci@c772bfc9b4c5:~/project$ tree
.
βββ setup.py
βββ src
β βββ __init__.py
β βββ main.py
βββ tests
βββ test_hello.py
2 directories, 4 files
circleci@c772bfc9b4c5:~/project$ cat setup.py
import setuptools
setuptools.setup(name="tmp-example",version="0.0.1",author="null",author_email="null",url="null",description="null",install_requires=[
'pytest',
])
circleci@c772bfc9b4c5:~/project$ pip install -e .
Obtaining file:///home/circleci/project
Requirement already satisfied: pytest in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from tmp-example==0.0.1) (6.1.1)
Requirement already satisfied: attrs>=17.4.0 in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from pytest->tmp-example==0.0.1) (20.2.0)
Requirement already satisfied: iniconfig in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from pytest->tmp-example==0.0.1) (1.1.1)
Requirement already satisfied: py>=1.8.2 in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from pytest->tmp-example==0.0.1) (1.9.0)
Requirement already satisfied: pluggy<1.0,>=0.12 in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from pytest->tmp-example==0.0.1) (0.13.1)
Requirement already satisfied: packaging in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from pytest->tmp-example==0.0.1) (20.4)
Requirement already satisfied: toml in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from pytest->tmp-example==0.0.1) (0.10.1)
Requirement already satisfied: pyparsing>=2.0.2 in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from packaging->pytest->tmp-example==0.0.1) (2.4.7)
Requirement already satisfied: six in /home/circleci/.pyenv/versions/3.9.0/lib/python3.9/site-packages (from packaging->pytest->tmp-example==0.0.1) (1.15.0)
Installing collected packages: tmp-example
Running setup.py develop for tmp-example
Successfully installed tmp-example
WARNING: You are using pip version 20.2.3; however, version 20.2.4 is available.
You should consider upgrading via the '/home/circleci/.pyenv/versions/3.9.0/bin/python3.9 -m pip install --upgrade pip' command.
circleci@c772bfc9b4c5:~/project$ echo $PYTHONPATH
circleci@c772bfc9b4c5:~/project$ cat tests/test_hello.py
from src import main
def test_hw():
main.hello_world()
circleci@c772bfc9b4c5:~/project$ pytest
============================================================ test session starts ============================================================
platform linux -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/circleci/project
collected 1 item
tests/test_hello.py . [100%]
============================================================= 1 passed in 0.01s =============================================================
With this project structure, you can now use the following config without setting any environment variables.
- python/test:
pkg-manager: pip-dist
test-tool: pytest
Using an advance packaging tool
Itβs highly recommended that you use a higher level packaging tool like pipenv or poetry. poetry
is recommended over pipenv
.
Poetry
You can setup poetry
without a setup.py and just adding a pyproject.toml
. Notice this file specifies the packages (src
in this case) to create when setting up the environment.
circleci@5ccc46929985:~/project$ tree
.
βββ pyproject.toml
βββ src
β βββ __init__.py
β βββ main.py
βββ tests
βββ test_hello.py
2 directories, 4 files
circleci@5ccc46929985:~/project$ pytest
bash: pytest: command not found
circleci@5ccc46929985:~/project$ cat pyproject.toml
# Example toml for integration testing - this is not used by the orb in anyway
[tool.poetry]
authors=["Test"]
name="test"
description="none"
version = "0.0.1"
packages = [
{ include = "src" },
]
[tool.poetry.dependencies]
python = "*"
[tool.poetry.dev-dependencies]
pytest = "*"
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
circleci@5ccc46929985:~/project$ poetry install
Creating virtualenv test-3aSsmiER-py3.9 in /home/circleci/.cache/pypoetry/virtualenvs
Updating dependencies
Resolving dependencies... (14.7s)
Writing lock file
Package operations: 10 installs, 0 updates, 0 removals
β’ Installing pyparsing (2.4.7)
β’ Installing six (1.15.0)
β’ Installing atomicwrites (1.4.0)
β’ Installing attrs (20.2.0)
β’ Installing more-itertools (7.2.0)
β’ Installing packaging (20.4)
β’ Installing pluggy (0.13.1)
β’ Installing py (1.9.0)
β’ Installing wcwidth (0.2.5)
β’ Installing pytest (4.6.11)
Installing the current project: test (0.0.1)
circleci@5ccc46929985:~/project$ poetry run pytest
============================================================ test session starts ============================================================
platform linux -- Python 3.9.0, pytest-4.6.11, py-1.9.0, pluggy-0.13.1
rootdir: /home/circleci/project
collected 1 item
tests/test_hello.py . [100%]
========================================================= 1 passed in 0.07 seconds ==========================================================
Pipenv
To turn your package into a pipenv
project, you simply need to add a Pipfile
(but you need the setup.py
we created earlier)
circleci@23ea86f1d144:~/project$ tree
.
βββ Pipfile
βββ setup.py
βββ src
β βββ __init__.py
β βββ main.py
βββ tests
βββ test_hello.py
2 directories, 5 files
circleci@23ea86f1d144:~/project$ cat Pipfile
[packages]
pytest = "*"
tmp-example = {editable = true, path = "."}
circleci@23ea86f1d144:~/project$ pipenv install
Creating a virtualenv for this projectβ¦
Pipfile: /home/circleci/project/Pipfile
Using /home/circleci/.pyenv/versions/3.9.0/bin/python3.9 (3.9.0) to create virtualenvβ¦
β ΄ Creating virtual environment...created virtual environment CPython3.9.0.final.0-64 in 369ms
creator CPython3Posix(dest=/home/circleci/.local/share/virtualenvs/project-zxI9dQ-Q, clear=False, global=False)
seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/circleci/.local/share/virtualenv)
added seed packages: pip==20.2.3, setuptools==50.3.0, wheel==0.35.1
activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
β Successfully created virtual environment!
Virtualenv location: /home/circleci/.local/share/virtualenvs/project-zxI9dQ-Q
Pipfile.lock not found, creatingβ¦
Locking [dev-packages] dependenciesβ¦
Locking [packages] dependenciesβ¦
Building requirements...
Resolving dependencies...
β Success!
Updated Pipfile.lock (a4ad01)!
Installing dependencies from Pipfile.lock (a4ad01)β¦
π ββββββββββββββββββββββββββββββββ 10/10 β 00:00:03
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
circleci@23ea86f1d144:~/project$ pipenv run pytest
============================================================ test session starts ============================================================
platform linux -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/circleci/project
collected 1 item
tests/test_hello.py . [100%]
============================================================= 1 passed in 0.05s =============================================================
I have opened up a few issues on the python orb github page based on your feedback.