Pytest plugin for Blender testing. Executes your pytest testsuite with Blender in headless mode using its builtin Python interpreter.
pip install pytest-blender
Before execute it, you need to install your testing dependencies inside the
builtin Blender Python interpreter. To get the interpreter location you can
use the CLI utility pytest-blender
, something like:
blender_python="$(pytest-blender)"
$blender_python -m ensurepip
$blender_python -m pip install -r test-requirements.txt
After installing dependencies, just call pytest as usually.
pytest -svv
Blender 2.82 (sub 7)
Read prefs: ~/.config/blender/2.82/config/userpref.blend
=================== test session starts ===================
platform linux -- Python 3.8.5, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- /usr/bin/blender
cachedir: .pytest_cache
rootdir: /home/mondeja/files/code/pytest-blender
collected 1 item
tests/test_bpy_import.py::test_inside_blender <module 'bpy' from '/usr/share/blender/scripts/modules/bpy/__init__.py'>
PASSED
==================== 1 passed in 0.01s ====================
All options can be passed as a CLI argument like --[option-name]
or
defined inside a configuration file.
Specify a custom blender
executable location.
pytest --blender-executable ~/blender-2.91.2-linux64/blender
[pytest]
blender-executable = ~/blender-2.91.2-linux64/blender
Blender 2.91.2 (hash 5be9ef417703 built 2021-01-19 16:16:34)
Read prefs: ~/.config/blender/2.91/config/userpref.blend
found bundled python: ~/blender-2.91.2-linux64/2.91/python
=================== test session starts ===================
platform linux -- Python 3.7.7, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: ~/pytest-blender
collected 1 item
tests/test_bpy_import.py . [100%]
==================== 1 passed in 0.00s ====================
Load a custom startup .blend
template.
pytest -svv --blender-template ~/.config/blender/2.93/config/startup.blend
[pytest]
blender-template = ~/.config/blender/2.93/config/startup.blend
addopts = -svv
Install addons inside Blender before executing the test suite. This allows you to easily test them.
By "addons" Blender understands Python scripts whose file names
end with .py
, .zip
files for compressed packages with multiple modules
or directories for Python packages which contain a __init__.py
file.
These must be located in the root of each directory passed to
blender-addons-dirs
.
For example, given the next directory tree:
📁 addons-dirs
├── 📁 private-addons
│ └── 📁 package_addon
│ ├── 📄 __init__.py
│ └── 📄 main.py
|
└── 📁 public-addons
├── 📄 module_addon.py
└── 📄 compressed_addon.zip
├── 📄 __init__.py
└── 📄 main.py
The next configurations will install the addons package_addon
,
module_addon
and compressed_addon
.
pytest tests --blender-addons-dirs addons-dirs/private-addons addons-dirs/public-addons
[pytest]
blender-addons-dirs =
addons-dirs/private-addons
addons-dirs/public-addons
You can also define a unique addons directory in configuration files defining it as a string:
[pytest]
blender-addons-dirs = addons-dirs/public-addons
If you need more complex setups see the fixtures
install_addons_from_dir
,
disable_addons
and
uninstall_addons
.
Define the addons cleaning strategy to follow after executing your
test suite. It only affects to the addons installed using
blender-addons-dirs
.
It accepts one of the next values:
uninstall
(default): Uninstall the addons after executing the test suite.disable
: Just disable the addons in user preferences, but does not uninstall them.keep
: Keep the addons enabled. Useful if you want to manually review the addons or while you're developing.
pytest --blender-addons-cleaning disable
[pytest]
blender-addons-cleaning = disable
Show in STDOUT the command executed by pytest-blender executing your test suite.
pytest --pytest-blender-debug
[pytest]
pytest-blender-debug = true
[DEBUG (pytest-blender)] Running blender with: /usr/bin/blender -b --python /home/foo/files/code/pytest-blender/pytest_blender/run_pytest.py -- --pytest-blender-executable /usr/bin/blender -svv --rootdir=/tmp/tmpdsh0wnsf --strict-markers --strict-config -c /tmp/tmpdsh0wnsf/pytest.ini
Blender 2.82 (sub 7)
Read prefs: /home/foo/.config/blender/2.82/config/userpref.blend
=================== test session starts ===================
platform linux -- Python 3.8.10, pytest-7.0.1, pluggy-0.13.1 -- /usr/bin/blender
cachedir: .pytest_cache
rootdir: /tmp/tmpyio7hlc2, configfile: pytest.ini
plugins: cov-3.0.0, Faker-12.1.0
collecting ... collected 1 item
tests/test_foo.py::test_foo PASSED
==================== 1 passed in 0.09s ====================
#
blender_executable ⇒ str
Returns the path of the executable that has started the current Blender session.
# blender_version
⇒ str
Returns the version of Blender running in the current session.
#
blender_python_executable ⇒ str
Returns the path of the Python executable builtin in the Blender release of the currently running session.
#
blender_python_version ⇒ str
Returns the version of the Python executable builtin in the Blender release of the currently running session.
#
blender_addons_dir ⇒ str
Returns the scripts/addons
directory of Blender (see
Blender Directory Layout), the directory in which by default are located
the addons installed using the
install_addons_from_dir
fixture.
It tries to get it using the BLENDER_USER_SCRIPTS
environment variable, but
if is not defined attempts to discover it from the PATH
.
#
install_addons_from_dir(addons_dir, addon_ids=None,
save_userpref=True, default_set=True, persistent=True,
quiet=True, **kwargs) ⇒ list
Function that installs and enables a set of addons which are located in
a directory. By "addons" Blender understands Python scripts whose file names
end with .py
, .zip
files for compressed packages with multiple modules
or directories for Python packages which contain a __init__.py
file.
This function is designed to be executed before the pytest session
to install the addons that you want to test, using the others fixtures
disable_addons
or uninstall_addons
to disable or remove them after the execution of the test suite:
import pytest
@pytest.fixture(scope="session", autouse=True)
def register_addons(install_addons_from_dir, disable_addons):
addons_ids = install_addons_from_dir("src")
yield
disable_addons(addons_ids)
import pytest
@pytest.fixture(scope="session", autouse=True)
def register_addons(install_addons_from_dir, uninstall_addons):
addons_ids = install_addons_from_dir("src")
yield
uninstall_addons(addons_ids)
The difference between disabling addons and uninstalling them is that disabling removes the files from the Blender's addons directory but disabling keep the files there, allowing you to enable it manually from the preferences.
- addons_dir (str) Directory in whose root are located the files of the addons.
- addons_ids (list) Identifiers of the addons modules, packages or ZIP
files (without extensions) to install. If not defined (default) all Python
modules, Python packages and ZIP files containing addon packages or modules
located at the root of the
addons_dir
directory will be installed. These identifiers are either:- The name of the module for addons composed by a single file
(
[identifier].py
). - The name of the directory for addons composed by a package.
- The name of the ZIP file without extension for addons composed by a
ZIP file (
[identifier].zip
).
- The name of the module for addons composed by a single file
(
- save_userpref (bool) Save user preferences after installation calling
bpy.ops.wm.save_userpref
- default_set (bool) Set the user-preference calling
addon_utils.enable
. - persistent (bool) Ensure that the addon is enabled for the entire session, after loading new files.
- quiet (bool) If enabled, don't show standard output produced installing addons.
- **kwargs (dict) Subsecuent keyword arguments are passed to
bpy.ops.preferences.addon_install
.
Returns the addons identifiers as a list, ready to be passed to
disable_addons
or uninstall_addons
.
# disable_addons(addons_ids, save_userpref=True, default_set=True, quiet=True, **kwargs)
Function that disables a set of addons by addons identifiers. Is designed
to disable your addons after a pytest suite execution (check
install_addons_from_dir
for an example).
- addons_ids (list) Identifiers of the addons modules as are returned by
install_addons_from_dir
. - save_userpref (bool) Save user preferences after installation.
- default_set (bool) Set the user-preference calling
addon_utils.disable
. - quiet (bool) If enabled, don't show stdout produced disabling addons.
- **kwargs (dict) Subsecuent keyword arguments are passed to
addon_utils.disable
.
# uninstall_addons(addons_ids, quiet=True)
Function that uninstall a set of addons by addon identifiers. Is designed to
remove your addons from the Blender's addons directory after a pytest suite
execution (check install_addons_from_dir
for an example).
- addons_ids (list) Name of the addons modules as is returned by
install_addons_from_dir
. - quiet (bool) If enabled, don't show stdout produced disabling addons.
When you call pytest
, all options like --blender-executable
are passed
to the pytest
suite running pytest-blender
. If you want to pass arguments
to blender
in its headless execution, add a --
between pytest
and
blender
arguments.
For example:
pytest -svv --blender-executable ~/blender -- --debug
You can use pytest-cov to measure code coverage. If so, instead of
using blender-addons-dirs
configuration option
use pytest's pythonpath
option.
Example for pytest.ini:
[pytest]
pythonpath = path/to/blender/addons
addopts = --cov path/to/blender/addons
You can use blender-downloader to download multiple versions of Blender in your CI and test against them. There is an example for Github Actions in the CI configuration of this repository, something like:
jobs:
test:
name: Test
runs-on: ${{ matrix.platform }}
strategy:
matrix:
platform:
- ubuntu-latest
- macos-latest
blender-version:
- '3.1.2'
- '2.93.9'
- '2.83.9'
steps:
- uses: actions/checkout@v3
- name: Set up Python v3.9
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Upgrade PIP
run: python -m pip install --upgrade pip
- name: Cache Blender ${{ matrix.blender-version }}
uses: actions/cache@v3
id: cache-blender
with:
path: |
blender-*
_blender-executable-path.txt
key: ${{ runner.os }}-${{ matrix.blender-version }}
- name: Download Blender ${{ matrix.blender-version }}
if: steps.cache-blender.outputs.cache-hit != 'true'
id: download-blender
run: |
python -m pip install --upgrade blender-downloader
printf "%s" "$(blender-downloader \
${{ matrix.blender-version }} --extract --remove-compressed \
--quiet --print-blender-executable)" > _blender-executable-path.txt
- name: Install dependencies
id: install-dependencies
run: |
python -m pip install .[test]
blender_executable="$(< _blender-executable-path.txt)"
python_blender_executable="$(pytest-blender --blender-executable $blender_executable)"
$python_blender_executable -m ensurepip
$python_blender_executable -m pip install pytest
echo "blender-executable=$BLENDER_EXECUTABLE" >> $GITHUB_OUTPUT
- name: Test with pytest
run: pytest -svv --blender-executable \
"${{ steps.install-dependencies.outputs.blender-executable }}" tests
- Latest version that officially supports Python3.6 is v1.2.1.