Test and Deploy#
Tips for testing napari plugins#
Testing is a big topic! If you are completely new to writing tests in python, consider reading this post on Getting Started With Testing in Python
We recommend using pytest for testing your plugin. Aim for 100% test coverage!
The make_napari_viewer_proxy
fixture#
Testing a napari Viewer
requires some setup and teardown each time. We have
created a pytest fixture called
make_napari_viewer_proxy
that you can use (this requires that you have napari
installed in your environment).
To use a fixture in pytest, you simply include the name of the fixture in the test parameters (oddly enough, you don’t need to import it!). For example, to create a napari viewer for testing:
def test_something_with_a_viewer(make_napari_viewer_proxy):
viewer = make_napari_viewer_proxy()
... # carry on with your test
If you embed the viewer in your own application and need to access private attributes,
you can use the make_napari_viewer
fixture.
Prefer smaller unit tests when possible#
The most common issue people run into when designing tests for napari plugins is that they try to test everything as a full “integration test”, starting from the napari event or action that would trigger their plugin to do something. For example, let’s say you have a dock widget that connects a mouse callback to the viewer:
class MyWidget:
def __init__(self, viewer: 'napari.Viewer'):
self._viewer = viewer
@viewer.mouse_move_callbacks.append
def _on_mouse_move(viewer, event):
if 'Shift' in event.modifiers:
...
@napari_hook_implementation
def napari_experimental_provide_dock_widget():
return MyWidget
You might think that you need to somehow simulate a mouse movement in napari in
order to test this, but you don’t! Just trust that napari will call this
function with a Viewer
and an Event
when a mouse move has been made, and
otherwise leave napari
out of it.
Instead, focus on “unit testing” your code: just call the function directly with objects that emulate, or “mock” the objects that your function expects to receive from napari. You may also need to slightly reorganize your code. Let’s modify the above widget to make it easier to test:
class MyWidget:
def __init__(self, viewer: 'napari.Viewer'):
self._viewer = viewer
# connecting to a method rather than a local function
# makes it easier to test
viewer.mouse_move_callbacks.append(self._on_mouse_move)
def _on_mouse_move(self, viewer, event):
if 'Shift' in event.modifiers:
...
To test this, we can often just instantiate the widget with our own viewer, and
then call the methods directly. As for the event
object, notice that all we
care about in this plugin is that it has a modifiers
attribute that may or may
not contain the string "Shift"
. So let’s just fake it!
class FakeEvent:
modifiers = {'Shift'}
def test_mouse_callback(make_napari_viewer):
viewer = make_napari_viewer()
wdg = MyWidget(viewer)
wdg._on_mouse_move(viewer, FakeEvent())
# assert that what you expect to happen actually happened!
Preparing for release#
To help users find your plugin, make sure to use the Framework :: napari
classifier in your package’s core metadata. (If you used the cookiecutter,
this has already been done for you.)
Once your package is listed on PyPI (and includes the Framework :: napari
classifier), it will also be visible on the napari
hub. To ensure you are providing the relevant metadata and
description for your plugin, see the following documentation in the napari hub
GitHub’s docs
folder:
The hub
For more about the napari hub, see the napari hub About page. To learn more about the hub’s development process, see the napari hub GitHub’s Wiki.
If you want your plugin to be available on PyPI, but not visible on the napari
hub, you can add a .napari/config.yml
file to the root of your repository with
a visibility key. For details, see the customization
guide.
Finally, once you have curated your package metadata and description, you can preview your metadata, and check any missing fields using the napari hub preview page service. Check out this guide for instructions on how to set it up.
Deployment#
When you are ready to share your plugin, upload the Python package to
PyPI after which it will be installable using python -m pip install <yourpackage>
, or (assuming you added the Framework :: napari
classifier)
in the builtin plugin installer dialog.
If you used the Cookiecutter template, you can also setup automated deployments on github for every tagged commit.
What about conda?
While you are free to distribute your plugin on anaconda cloud in addition to or instead of PyPI, the built-in napari plugin installer doesn’t currently install from conda. In this case, you may guide your users to install your package on the command line using conda in your readme or documentation.
A future version of napari and the napari stand-alone application may support directly installing from conda.
When you are ready for users, announce your plugin on the Image.sc forum.