manim/docs/source/contributing/testing.rst
Nikhil Garuda 7c8c1cb1d9
Grammatical error corrections in documentation (#1360)
* Grammatical error correction of docs

* Update contributing.rst

* Update development.rst

* Update development.rst

* Update development.rst

* Update for_dev.rst

* Update docs/source/installation/for_dev.rst

Co-authored-by: Darylgolden <darylgolden@gmail.com>

* Update plugins.rst

* Update reporting_bugs.rst

* Update a_deeper_look.rst

* Update configuration.rst

* Update quickstart.rst

* Update building_blocks.rst

* Update testing.rst

* Update testing.rst

* Update versions.rst

* Update a_deeper_look.rst

* Update quickstart.rst

* Update quickstart.rst

* Update development.rst

* Update development.rst

* Update versions.rst

* Update versions.rst

* Update a_deeper_look.rst

* Update building_blocks.rst

* Change of complier and output format in
 "InCodeTexTemplate" to the defaults

* Reverted back

* Change of Complier and Output format

The Complier and Output format of class InCodeTexTemplate has been changed to the defaults (latex and .dvi)

* Update customtex.py

* Update building_blocks.rst

* Update building_blocks.rst

* Update a_deeper_look.rst

Co-authored-by: Darylgolden <darylgolden@gmail.com>
Co-authored-by: kilacoda <65204531+kilacoda@users.noreply.github.com>
Co-authored-by: Jason Villanueva <a@jsonvillanueva.com>
2021-04-19 15:44:32 -07:00

307 lines
10 KiB
ReStructuredText

============
Adding Tests
============
If you are adding new features to manim, you should add appropriate tests for them. Tests prevent
manim from breaking at each change by checking that no other
feature has been broken and/or been unintentionally modified.
How Manim Tests
---------------
Manim uses pytest as its testing framework.
To start the testing process, go to the root directory of the project and run pytest in your terminal.
Any errors that occur during testing will be displayed in the terminal.
Some useful pytest flags:
- ``-x`` will make pytest stop at the first failure it encounters
- ``-s`` will make pytest display all the print messages (including those during scene generation, like DEBUG messages)
- ``--skip_slow`` will skip the (arbitrarily) slow tests
- ``--show_diff`` will show a visual comparison in case an unit test is failing.
How it Works
~~~~~~~~~~~~
At the moment there are three types of tests:
#. Unit Tests:
Tests for most of the basic functionalities of manim. For example, there a test for
``Mobject``, that checks if it can be added to a Scene, etc.
#. Graphical unit tests:
Because ``manim`` is a graphics library, we test frames. To do so, we create test scenes that render a specific feature.
When pytest runs, it compares the last frame of every render to the control data; If it matches, the tests
pass. If the test and control data differ, the tests fail. You can
use ``--show_diff`` flag with ``pytest`` to visually see the differences.
#. Videos format tests:
As Manim is a video library, we have to test videos as well. Unfortunately,
we cannot directly test video content as rendered videos can
differ slightly depending on the system (for reasons related to
ffmpeg). Therefore, we only compare video configuration values, exported in
.json.
Architecture
------------
The ``manim/tests`` directory looks like this:
::
.
├── conftest.py
├── control_data
│ ├── graphical_units_data
│ │ ├── creation
│ │ │ ├── DrawBorderThenFillTest.npy
│ │ │ ├── FadeInFromDownTest.npy
│ │ │ ├── FadeInFromLargeTest.npy
│ │ │ ├── FadeInFromTest.npy
│ │ │ ├── FadeInTest.npy
│ │ │ ├── ...
│ │ ├── geometry
│ │ │ ├── AnnularSectorTest.npy
│ │ │ ├── AnnulusTest.npy
│ │ │ ├── ArcBetweenPointsTest.npy
│ │ │ ├── ArcTest.npy
│ │ │ ├── CircleTest.npy
│ │ │ ├── CoordinatesTest.npy
│ │ │ ├── ...
│ │ ├── graph
│ │ │ ├── ...
| | | | ...
│ └── videos_data
│ ├── SquareToCircleWithDefaultValues.json
│ └── SquareToCircleWithlFlag.json
├── helpers
│ ├── graphical_units.py
│ ├── __init__.py
│ └── video_utils.py
├── __init__.py
├── test_camera.py
├── test_config.py
├── test_container.py
├── test_copy.py
├── test_vectorized_mobject.py
├── test_graphical_units
│ ├── conftest.py
│ ├── __init__.py
│ ├── test_creation.py
│ ├── test_geometry.py
│ ├── test_graph.py
│ ├── test_indication.py
│ ├── test_movements.py
│ ├── test_threed.py
│ ├── test_transform.py
│ └── test_updaters.py
├── test_logging
│ ├── basic_scenes.py
│ ├── expected.txt
│ ├── testloggingconfig.cfg
│ └── test_logging.py
├── test_scene_rendering
│ ├── conftest.py
│ ├── __init__.py
│ ├── simple_scenes.py
│ ├── standard_config.cfg
│ └── test_cli_flags.py
└── utils
├── commands.py
├── GraphicalUnitTester.py
├── __init__.py
├── testing_utils.py
└── video_tester.py
...
The Main Directories
--------------------
- ``control_data/``:
The directory containing control data. ``control_data/graphical_units_data/`` contains the expected and correct frame data for graphical tests, and
``control_data/videos_data/`` contains the .json files used to check videos.
- ``test_graphical_units/``:
Contains graphical tests.
- ``test_scene_rendering/``:
For tests that need to render a scene in some way, such as tests for CLI
flags (end-to-end tests).
- ``utils/``:
Useful internal functions used by pytest.
.. Note:: fixtures are not contained here, they are in ``conftest.py``.
- ``helpers/``:
Helper functions for developers to setup graphical/video tests.
Adding a New Test
-----------------
Unit Tests
~~~~~~~~~~
Pytest determines which functions are tests by searching for files whose
names begin with "test\_", and then within those files for functions
beginning with "test" and classes beginning with "Test". These kinds of
tests must be in ``tests/`` (e.g. ``tests/test_container.py``).
Graphical Unit Test
~~~~~~~~~~~~~~~~~~~
The test must be written in the correct file (i.e. the file that corresponds to the appropriate category the feature belongs to) and follow the structure
of unit tests.
For example, to test the ``Circle`` VMobject which resides in
``manim/mobject/geometry.py``, add the CircleTest to
``test/test_geometry.py``.
In ``test_geometry.py``, add:
.. code:: python
class CircleTest(Scene):
def construct(self):
circle = Circle()
self.play(Animation(circle))
Scene names follow the syntax: ``<thing_to_test>Test``. In the example above,
we are testing whether Circle properly shows up with the generic
``Animation`` and not any specific animation.
.. Note::
If the file already exists, edit it and add the test within the file. The
``Scene`` will be tested thanks to the ``GraphicalUnitTester`` that lives
in ``tests/utils/GraphicalUnitTester.py``. Import it with ``from
..utils.GraphicalUnitTester import GraphicalUnitTester``.
To test all the scenes in the module, we do the following:
.. code:: python
@pytest.mark.parametrize("scene_to_test", get_scenes_to_test(__name__), indirect=False)
def test_scene(scene_to_test, tmpdir, show_diff):
GraphicalUnitTester(scene_to_test[1], MODULE_NAME, tmpdir).test(show_diff=show_diff)
The first line is a `pytest decorator
<https://docs.pytest.org/en/stable/parametrize.html>`_.
It is used to run a test function several times with different
parameters. Here, we pass in all the scenes as arguments.
.. warning::
If you run pytest now, you will get a ``FileNotFound`` error. This is because
you have not created control data for your test.
Next, we'll want to create control data for ``CircleTest``. In
``tests/template_generate_graphical_units_data.py``, there exists the
function, ``set_test_scene``, for this purpose.
It will look something like this:
.. code:: python
class CircleTest(Scene):
def construct(self):
circle = Circle()
self.play(Animation(circle))
set_test_scene(CircleTest, "geometry")
``set_test_scene`` takes two parameters: the scene to test, and the
module name. You can generate the test data by running the file (it suffices to type the name of the file in the terminal; you do not have to run
it like how you would normally run manim files). It will automatically generate the control data in the
right directory (in this case,
``tests/control_data/graphical_units_data/geometry/CircleTest.npz``).
Please make sure to add the control data to git as
soon as it is produced with ``git add <your-control-data.npz>``. However, do not
include changes to the template script (template\_generate\_graphical\_units\_data.py) in your pull request so that others
may continue to use the unmodified file to generate their own tests.
Videos tests
~~~~~~~~~~~~
To test videos generated, we use the decorator
``tests.utils.videos_tester.video_comparison``:
.. code:: python
@video_comparison(
"SquareToCircleWithlFlag.json", "videos/simple_scenes/480p15/SquareToCircle.mp4"
)
def test_basic_scene_l_flag(tmp_path, manim_cfg_file, simple_scenes_path):
scene_name = "SquareToCircle"
command = [
"python",
"-m",
"manim",
simple_scenes_path,
scene_name,
"-l",
"--media_dir",
str(tmp_path),
]
out, err, exit_code = capture(command)
assert exit_code == 0, err
.. Note:: ``assert exit*\ code == 0, err`` is used in case of the command fails
to run. The decorator takes two arguments: json name and the path
to where the video should be generated, starting from the ``medias/`` dir.
Note the fixtures here:
- tmp_path is a pytest fixture to get a tmp_path. Manim will output here, according to the flag ``--media_dir``.
- ``manim_cfg_file`` fixture that return a path pointing to ``test_scene_rendering/standard_config.cfg``. It's just to shorten the code, in the case multiple tests need to use this cfg file.
- ``simple_scenes_path`` same as above, except for ``test_scene_rendering/simple_scene.py``
You have to generate a ``.json`` file first to be able to test your video. To
do that, use ``helpers.save_control_data_from_video``.
For instance, a test that will check if the l flag works properly will first
require rendering a video using the -l flag from a scene. Then we will test
(in this case, SquareToCircle), that lives in
``test_scene_rendering/simple_scene.py``. Change directories to ``tests/``,
create a file (e.g. ``create\_data.py``) that you will remove as soon as
you're done. Then run:
.. code:: python
save_control_data_from_video("<path-to-video>", "SquareToCircleWithlFlag.json")
Running this will save
``control_data/videos_data/SquareToCircleWithlFlag.json``, which will
look like this:
.. code:: json
{
"name": "SquareToCircleWithlFlag",
"config": {
"codec_name": "h264",
"width": 854,
"height": 480,
"avg_frame_rate": "15/1",
"duration": "1.000000",
"nb_frames": "15"
}
}
If you have any questions, please don't hesitate to ask on `Discord
<https://discord.gg/mMRrZQW>`_, in your pull request, or in an issue.