============
Adding Tests
============
If you add a feature or fix a bug in Manim, you should also add tests that
cover the changed behavior. Tests help prevent later changes from breaking
existing functionality.
.. warning::
The full test suite requires Cairo 1.18 or later. If an older version of
Cairo is installed, graphical tests that depend on Cairo are skipped
locally, but they still run in continuous integration (CI).
If Cairo 1.18 is not available from your package manager, download a recent
release from the `Cairo releases page
`_ and follow the installation
instructions included with it.
Running the Test Suite
----------------------
Manim uses `pytest `_ as its test framework. From
the root directory of the repository, run the test suite with:
.. code-block:: bash
uv run pytest
The following pytest options are particularly useful while developing tests:
``-x``
Stop after the first failure.
``-s``
Show output written while the test is running, including Manim's render
logs.
``--skip_slow``
Skip tests marked as slow.
``--show_diff``
Open a visual comparison when a graphical test fails.
You can also run a single file or test by passing its path and node ID. For
example:
.. code-block:: bash
uv run pytest tests/test_graphical_units/test_geometry.py::test_Circle
To run the doctests embedded in Manim's source code, use:
.. code-block:: bash
uv run pytest --doctest-modules manim
Types of Tests
--------------
Manim's test suite contains three broad kinds of tests:
Unit tests
These test individual functions, classes, and other behavior that can be
checked directly. Most unit tests live in ``tests/module/``.
Graphical tests
These render a small scene and compare the resulting frame data against a
committed ``.npz`` control file. A graphical test can compare only the last
frame or multiple frames from an animation. Graphical tests live in
``tests/test_graphical_units/``.
Render and output tests
These exercise scene rendering, command-line options, caching, sections,
and generated files. Video comparison tests check metadata and section
output recorded in committed JSON control files. They do not compare the
encoded video pixel by pixel because encoding can vary between systems.
Most of these tests live in ``tests/test_scene_rendering/``.
Test Directory Layout
---------------------
The main testing directories are organized as follows:
::
tests/
├── conftest.py
├── control_data/
│ └── videos_data/
├── helpers/
│ ├── graphical_units.py
│ └── video_utils.py
├── module/
├── test_graphical_units/
│ ├── control_data/
│ └── test_*.py
├── test_scene_rendering/
└── utils/
└── video_tester.py
``conftest.py``
Defines shared fixtures and custom pytest options.
``control_data/``
Stores expected data used by tests. Video metadata and section layouts are
stored in ``tests/control_data/videos_data/``. Graphical frame data is
stored next to the graphical tests in
``tests/test_graphical_units/control_data/``.
``helpers/``
Contains utilities for generating control data.
``module/``
Contains unit tests grouped to mirror Manim's source modules.
``test_graphical_units/``
Contains frame-comparison tests and their control data.
``test_scene_rendering/``
Contains tests that render scenes or exercise output-related behavior.
``utils/``
Contains internal utilities used by the test suite. Shared pytest fixtures
belong in a ``conftest.py`` file rather than this directory.
Adding a Unit Test
------------------
Pytest discovers files named ``test_*.py``. Within those files, it discovers
functions named ``test_*`` and classes named ``Test*``. Add unit tests to the
directory that corresponds to the source module being tested; for example,
tests for vectorized mobjects belong in
``tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py``.
Keep each test focused on one behavior. When fixing a bug, add a regression
test that fails without the fix and passes with it.
Adding a Graphical Test
-----------------------
Use a graphical test when correctness is best expressed by how a scene looks.
Place it in the appropriate file under ``tests/test_graphical_units/`` and use
the ``frames_comparison`` decorator. Every graphical test module must define
``__module_test__``; this value determines the subdirectory that stores its
control data.
The decorated test must accept a parameter named ``scene``. Use it in the same
way that you would use ``self`` in a scene's ``construct`` method:
.. code-block:: python
from manim import Circle
from manim.utils.testing.frames_comparison import frames_comparison
__module_test__ = "geometry"
@frames_comparison
def test_Circle(scene):
scene.add(Circle())
The decorator can be used with or without parentheses. By default, only the
last rendered frame is compared. Set ``last_frame=False`` when the intermediate
frames of an animation are significant:
.. code-block:: python
from manim import Circle, Create
@frames_comparison(last_frame=False)
def test_CircleCreation(scene):
scene.play(Create(Circle()))
You can also select a different base scene when necessary:
.. code-block:: python
from manim import ThreeDScene
@frames_comparison(base_scene=ThreeDScene)
def test_ThreeDCircle(scene):
scene.add(Circle())
See the documentation of ``frames_comparison`` for the complete set of
options.
Generating graphical control data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A new graphical test initially fails because its control data does not exist.
After carefully checking that the rendered result is correct, generate the
control file with ``--set_test``. For example, if you add a new test named
``test_NewCircle`` to ``test_geometry.py``, run:
.. code-block:: bash
uv run pytest tests/test_graphical_units/test_geometry.py::test_NewCircle --set_test -s
This writes ``tests/test_graphical_units/control_data/geometry/NewCircle.npz``.
Review the generated frames before committing them. The frame extraction script
converts the control data into PNG files for inspection:
.. code-block:: bash
uv run python scripts/extract_frames.py \
tests/test_graphical_units/control_data/geometry/NewCircle.npz output
The output directory will contain ``frame0.png``, ``frame1.png``, and so on.
Commit the reviewed ``.npz`` file together with the test.
When a graphical test fails against existing control data, rerun it with
``--show_diff`` to compare the newly rendered frame with the expected frame:
.. code-block:: bash
uv run pytest tests/test_graphical_units/test_geometry.py::test_Circle --show_diff
The comparison highlights differing pixels, making it easier to tell whether
the change is intentional or a regression. Inspect the difference before using
``--set_test`` to replace existing control data.
Adding a Video Comparison Test
------------------------------
Use ``tests.utils.video_tester.video_comparison`` when a test needs to verify
the metadata and section files produced by a rendered video. The decorator
takes the name of a JSON control file and the expected path of the generated
video relative to the media directory:
.. code-block:: python
import sys
from manim import capture
from tests.utils.video_tester import video_comparison
@video_comparison(
"SquareToCircleWithlFlag.json",
"videos/simple_scenes/480p15/SquareToCircle.mp4",
)
def test_basic_scene_l_flag(tmp_path, simple_scenes_path):
command = [
sys.executable,
"-m",
"manim",
"-ql",
"--media_dir",
str(tmp_path),
str(simple_scenes_path),
"SquareToCircle",
]
_, err, exit_code = capture(command)
assert exit_code == 0, err
Here, ``tmp_path`` is pytest's temporary-directory fixture. The test directs
Manim's output there, and the decorator locates the generated video relative
to that directory. Fixtures such as ``simple_scenes_path`` are defined in the
nearest ``conftest.py`` file.
Generating video control data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Render the scene using the same options as the test, inspect the result, and
then pass the generated video path to
``tests.helpers.video_utils.save_control_data_from_video``:
.. code-block:: python
from pathlib import Path
from tests.helpers.video_utils import save_control_data_from_video
save_control_data_from_video(
Path(""),
"SquareToCircleWithlFlag",
)
This writes
``tests/control_data/videos_data/SquareToCircleWithlFlag.json``. The file
records the movie metadata, section directory layout, and section index used by
the comparison test. Review and commit the JSON control file with the test.
If you have questions, ask on `Discord
`_, in your pull request, or in an issue.