I have my unittests in a top level tests/
folder for my Python project that uses poetry (link to code). When I run my tests locally, I simply run:
poetry run python -m unittest discover -s tests/
Now, I want to run this as CI in Github Actions. I added the following workflow file:
name: Tests
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.9"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint poetry
- name: Run tests
run: poetry run python -m unittest discover -s tests/ -p '*.py'
- name: Lint
run: pylint $(git ls-files '*.py')
But, this fails (link to logs):
Run poetry run python -m unittest discover -s tests/ -p '*.py'
Creating virtualenv fixed-income-annuity-WUaNY9r8-py3.9 in /home/runner/.cache/pypoetry/virtualenvs
E
======================================================================
ERROR: tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/home/runner/work/fixed_income_annuity/fixed_income_annuity/tests/tests.py", line 3, in <module>
from main import Calculator
File "/home/runner/work/fixed_income_annuity/fixed_income_annuity/main.py", line 6, in <module>
from dateutil.relativedelta import relativedelta
ModuleNotFoundError: No module named 'dateutil'
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
Error: Process completed with exit code 1.
Why does this fail in Github Actions but works fine locally? I understand I can go down the venv
route to make it work but I prefer poetry run
since its a simple 1 liner.
You need to install the dependencies in your action in order for it to work. Adding poetry install
after your pip statement is an immediate fix, but there are some further tweaks you should make.
Your project needs to be tweaked for pytest to pick up your tests.
pytest requires that your files be prefixed with test_
, and all test classes should start with Test
.
You should have poetry manage pytest and pylint as dev dependencies so that they are installed within the venv only when you include them in your github actions (and locally) by running poetry install --with dev
:
# in pyproject.toml
[tool.poetry.group.dev.dependencies]
pytest = "^7.4.3"
pylint = "^3.0.2"
You'll also want to include the current directory in your pythonpath for pytest:
# in pyproject.toml
[tool.pytest.ini_options]
pythonpath = [
"."
]
You'll also want to add an init-hook for pylint to handle imports correctly while in the venv:
[MASTER]
init-hook='import sys; sys.path.append(".")'
# ...
From there, you can just use poetry to manage both pytest and pylint.
# snippet of test.yml
# ...
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install --with dev
- name: Run tests
run: poetry run pytest
- name: Lint
run: poetry run pylint $(git ls-files '*.py')