Pytest, VScode and Maya
June 26, 2022Introduction
So you’re using VScode and you’re into unit testing but unfortunately for you, you’re a maya user and you have to resort to running your tests in the command line.
Rejoice! You can actually get the VSCode test explorer to work with mayapy though the setup can be a bit hacky.
Info
This blog post assumes you’re running Windows, Python 3 and Maya 2022+ But you should be able to adapt this for any other OS, Python 2 and earlier versions of Maya (And likely other test libraries too.).
Maya Project Structure
You’ll need a project folder structured as a maya module looking similar to this. The actual template project can be found here on github.
maya-pytest-template/
├── .venv/
├── .vscode/
│ └── settings.json
├── scripts/
│ ├── maya_pytest_template/
│ │ └── __init__.py
│ └── userSetup.py
├── tests/
│ ├── __init__.py
│ ├── test_maya_pytest_template.py
│ └── conftest.py
├── mayapy.bat
├── pytest.bat
└── maya_pytest_template.mod
If you've worked with maya modules before, this should look pretty familiar with a few exceptions we'll cover in a minute.
If you haven't you can read up on that more here:
Setting up Pytest to work with Maya
Create a virtualenv
Let’s first create a virtualenv and install pytest in it. In a terminal type this:
python -m venv .venv
.venv/scripts/activate
pip install pytest
Setup mayapy.bat
Now let's create a file called mayapy.bat
and set its content to this.
1 rem mayapy.bat
2
3 @ECHO OFF
4
5 set MAYA_APP_DIR=%~dp0\temp\maya
6 set MAYA_MODULE_PATH=%~dp0;%MAYA_MODULE_PATH%
7 set PYTHONPATH=%~dp0\.venv\Lib\site-packages;%PYTHONPATH%
8
9 for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\Maya\2023\Setup\InstallPath" /v MAYA_INSTALL_LOCATION') do set "MAYA_LOCATION=%%~b"
10 "%MAYA_LOCATION%\bin\mayapy.exe" %*
Let’s break it down a bit:
@ECHO OFF
makes it so the content of the script is not printed to the console.set MAYA_APP_DIR=%~dp0\temp\maya
lets us use a customMAYA_APP_DIR
instead of using the default one fromDocuments/maya
This lets us start mayapy with zero bloat and will improve startup time significantly.set MAYA_MODULE_PATH=%~dp0;%MAYA_MODULE_PATH%
Adds the current folder to theMAYA_MODULE_PATH
which ensures that our module gets loaded properly.set PYTHONPATH=%~dp0\.venv\Lib\site-packages;%PYTHONPATH%
adds our virtualenv to the python path, making mayapy aware of all of our dependencies.for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\Maya\2023\Setup\InstallPath" /v MAYA_INSTALL_LOCATION') do set "MAYA_LOCATION=%%~b"
Oooh scary. This dynamically finds the install location of Maya 2023 using Windows’ registry and sets the variableMAYA_LOCATION
variable."%MAYA_LOCATION%\bin\mayapy.exe" %*
runs mayapy from theMAYA_LOCATION
and forwards all the command line arguments we passed tomayapy.bat
We can now call mayapy.bat
as we would any python interpreter except our environment is loaded properly:
./mayapy.bat -m site
sys.path = [
'D:\\projects\\maya\\maya-pytest-template', <─────────────────────────────────┬── our project is properly added to the PYTHONPATH
'D:\\projects\\maya\\maya-pytest-template\\.venv\\Lib\\site-packages', <──────┘
...
]
...
Initialize maya.standalone
While we can call mayapy.bat -m pytest
to run our tests, they currently fail because maya.standalone
is not initialized yet.
In this case, the tests fail because our Maya Module is not yet loaded:
./mayapy.bat -m pytest tests
====================================================== test session starts ======================================================
platform win32 -- Python 3.9.7, pytest-7.1.2, pluggy-1.0.0
rootdir: D:/projects/maya/maya-pytest-template
collected 0 items / 1 error
============================================================ ERRORS =============================================================
______________________________________ ERROR collecting tests/test_maya_pytest_template.py ______________________________________
ImportError while importing test module 'D:/projects/maya/maya-pytest-template/tests/test_maya_pytest_template.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
C:/Program Files/Autodesk/Maya2023/Python/lib/importlib/__init__.py:127: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_maya_pytest_template.py:5: in <module>
import maya_pytest_template
E ModuleNotFoundError: No module named 'maya_pytest_template'
==================================================== short test summary info ====================================================
ERROR tests/test_maya_pytest_template.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
======================================================= 1 error in 0.14s ========================================================
Pytest has a couple hooks that let us run some code whenever the test sessions start and end, let's set those up.
Let's write this in out conftest.py
:
# tests/conftest.py
import maya.standalone
import pytest
def pytest_sessionstart(session: pytest.Session):
maya.standalone.initialize()
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
maya.standalone.uninitialize()
Let’s try again.
.\mayapy.bat -m pytest tests
====================================================== test session starts ======================================================
platform win32 -- Python 3.9.7, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\projects\maya\maya-pytest-template
collected 2 items
tests\test_maya_pytest_template.py .. [100%]
======================================================= 2 passed in 0.16s =======================================================
Success! 🎉
Note
There is a slight difference when calling pytest as a module rather than using the
pytest
command directly but this should have little to no impact how things work.
Making it work in VSCode
The first thing we need to do is to let VScode know that we want to use pytest as our testing library.
Let’s add this 3 lines in our workspace settings:
// .vscode/settings.json
{
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["tests"]
}
Tip
You can also use the
Python: Configure tests
task from the command palette to automatically add these entries to your workspace settings.
Fixing Discovery Errors
If we now go to the testing tab we are greeted by a “Pytest Discovery Error”.
This is because we’re importing maya libraries in both our conftest.py
and our different tests.
At this point, vscode is still using the interpreter from our virtualenv and has no knowledge of those libraries.
We will properly address this when we’ll want to actually run the tests but for now, let’s refactor our tests a bit so that they can be discovered by any python interpreter.
A simple way to address this problem is to add a safeguard around all our maya imports:
# tests/conftest.py
import pytest
# Whether the tests are running in mayapy or not.
HAS_MAYA: bool
try:
import maya.standalone
except ImportError:
HAS_MAYA = False
else:
HAS_MAYA = True
def pytest_sessionstart(session: pytest.Session):
# Initialize maya standalone only if it is running in mayapy.
if HAS_MAYA:
maya.standalone.initialize()
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
# Uninitialize maya standalone only if it is running in mayapy.
if HAS_MAYA:
maya.standalone.uninitialize()
# tests/test_maya_pytest_template.py
from .conftest import HAS_MAYA
# Safeguard maya imports to avoid test discovery errors
if HAS_MAYA:
import maya_pytest_template
from maya import cmds
def test_version():
assert maya_pytest_template.__version__ == "0.0.1"
def test_create_node():
node = cmds.createNode("transform")
assert node == "transform1"
And just like that, we have our tests discovered! But we can’t run them yet.
Running the tests
Now is the time where we need to make VScode actually use mayapy.
Unfortunately we can’t specify a different interpreter for tests than for the rest of VScode.
And since our dependencies exist in our virtualenv - not in mayapy - we definitely want to keep that interpreter as the one used for most of vscode’s.
Luckily, vscode lets us use a custom pytest executable and we can hack around that to trick vscode into running our mayapy.bat
script.
To do that, we’ll need to make another bat file, let’s call it pytest.bat
:
rem pytest.bat
@ECHO OFF
".\mayapy.bat" -m pytest %*
Simple enough.
- We use
@ECHO OFF
for the same reason we did inmayapy.bat
.
We don’t want stdout to be cluttered by unwanted things, this would confuse vscode. - With
".\mayapy.bat" -m pytest %*
we callmayapy.bat
and ask it to run pytest as a module.
Again, we forward all the command line arguments with%*
so that we can usepytest.bat
as if we were using the actual executable directly.
We can use pytest.bat
like we would use the regular pytest executable now:
.\pytest.bat tests
====================================================== test session starts ======================================================
platform win32 -- Python 3.9.7, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\projects\maya\maya-pytest-template
collected 2 items
tests\test_maya_pytest_template.py .. [100%]
======================================================= 2 passed in 0.08s =======================================================
Cool!
Now let’s tell vscode to use our new pytest.bat
file as its pytest executable.
In our workspace settings let’s add this line:
// .vscode/settings.json
{
// Previous config ...
"python.testing.pytestPath": "${workspaceFolder}/pytest.bat"
}
And voilà!
Debugging the tests
Now that our tests are running, the last step is to be able to debug them within VScode as well.
This part is going to be less hacky as VScode has support for custom debug configurations through the launch.json
file.
To generate a new configuration:
- Go in the Run and Debug tab and click
create a launch.json file
- Then select
Python
- And finally
Python File
Now that the launch.json
file has been created, we need to make a few modifications to it.
Here’s the final file we’ll use:
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "MayaPy: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "internalConsole",
"justMyCode": true,
"python": "${workspaceFolder}/mayapy.bat",
"purpose": ["debug-test", "debug-in-terminal"]
}
]
}
Here’s the modifications we made to the one vscode generated:
- Changed the name to
MayaPy: Current File
- Set
console
tointernalConsole
to use the debug console instead of the terminal (Optional) - Set
python
to use ourmayapy.bat
- Set the
purpose
to["debug-test", "debug-in-terminal"]
This one does two things-
debug-test
makes this configuration the one that vscode will use when debugging the tests. -
debug-in-terminal
tells vscode to use this configuration when using theDebug Python File
task.This one is not necessary for what we’re doing but it is a pretty handy feature and there’s no reason not to include it.
Warning
maya.standalone.initialize
is not called automatically with this so you will need to manually call it in the file you’re debugging.
-
That’s all folks
We can now Discover, Run and Debug Maya tests as if we were real python developers… 🙃