添加测试¶
如果您正在向manim添加新功能,您应该为它们添加适当的测试。测试通过检查没有其他功能被破坏和/或被意外修改来防止manim在每次更改时出现故障。
警告
完整的测试套件需要Cairo 1.18才能运行所有测试。然而,您的包管理器(例如 apt
)可能不提供Cairo 1.18,并且您很可能安装了旧版本,例如1.16。如果您使用1.18之前的版本运行测试,许多测试将被跳过。这些测试在在线CI中不会被跳过。
如果您想在本地运行所有测试,需要安装Cairo 1.18或更高版本。您可以通过从源代码编译Cairo来实现:
从这里下载
cairo-1.18.0.tar.xz
并解压;打开INSTALL文件并按照说明操作(您可能需要安装
meson
和ninja
);运行测试套件并验证Cairo版本是否正确。
Manim如何测试¶
Manim使用pytest作为其测试框架。要开始测试过程,请转到项目的根目录并在终端中运行pytest。测试期间发生的任何错误都将显示在终端中。
一些有用的pytest标志
-x
将使pytest在遇到第一个失败时停止-s
将使pytest显示所有打印消息(包括场景生成期间的,如DEBUG消息)--skip_slow
将跳过(任意)慢速测试--show_diff
将在单元测试失败时显示视觉比较。
工作原理¶
目前有三种类型的测试
单元测试
manim大多数基本功能的测试。例如,有一个针对
Mobject
的测试,检查它是否可以添加到场景中等等。图形单元测试:由于
manim
是一个图形库,我们测试帧。为此,我们创建测试场景来渲染特定功能。当pytest运行时,它会将测试结果与控制数据进行比较,可以是6 fps,也可以只是最后一帧。如果匹配,则测试通过。如果测试数据和控制数据不同,则测试失败。您可以将--show_diff
标志与pytest
一起使用,以视觉方式查看差异。extract_frames.py
脚本可以让你看到测试的所有帧。视频格式测试
由于Manim是一个视频库,我们也必须测试视频。不幸的是,我们无法直接测试视频内容,因为渲染的视频可能会因系统而异(由于ffmpeg相关的原因)。因此,我们只比较视频配置值,这些值以.json格式导出。
架构¶
manim/tests
目录结构如下
.
├── 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_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
├── __init__.py
├── testing_utils.py
└── video_tester.py
...
主要目录¶
control_data/
:包含控制数据的目录。
control_data/graphical_units_data/
包含图形测试的预期和正确帧数据,control_data/videos_data/
包含用于检查视频的.json文件。test_graphical_units/
:包含图形测试。
test_scene_rendering/
:用于需要以某种方式渲染场景的测试,例如CLI标志的测试(端到端测试)。
utils/
:pytest使用的有用内部函数。
注意
fixtures不包含在此处,它们位于
conftest.py
中。helpers/
:用于开发人员设置图形/视频测试的辅助函数。
添加新测试¶
单元测试¶
Pytest通过搜索名称以“test_”开头的文件,然后在这些文件中搜索以“test”开头的函数和以“Test”开头的类来确定哪些函数是测试。这些类型的测试必须在 tests/
中(例如 tests/test_container.py
)。
图形单元测试¶
测试必须写入正确的文件(即与功能所属的适当类别相对应的文件)并遵循单元测试的结构。
例如,要测试位于 manim/mobject/geometry.py
中的 Circle
VMobject,请将CircleTest添加到 test/test_geometry.py
。
模块名称由变量__module_test__指示,该变量在任何图形测试文件中必须声明。模块名称用于存储图形控制数据。
重要提示
您需要使用 frames_comparison
装饰器来创建测试。测试函数必须接受一个名为 scene
的参数,该参数将像标准 construct
方法中的 self
一样使用。
这是 test_geometry.py
中的一个例子:
from manim import *
from manim.utils.testing.frames_comparison import frames_comparison
__module_test__ = "geometry"
@frames_comparison
def test_circle(scene):
circle = Circle()
scene.play(Animation(circle))
装饰器可以带括号或不带括号使用。默认情况下,测试只测试最后一帧。要启用多帧测试,您必须在参数中设置 ``last_frame=False``。
@frames_comparison(last_frame=False)
def test_circle(scene):
circle = Circle()
scene.play(Animation(circle))
您还可以根据需要指定所需的基础场景(例如ThreeDScene)。
@frames_comparison(last_frame=False, base_scene=ThreeDScene)
def test_circle(scene):
circle = Circle()
scene.play(Animation(circle))
请随意查看 @frames_comparison
的文档以获取更多信息。
请注意,测试名称必须遵循 test_<要测试的内容>
语法,否则pytest将无法将其识别为测试。
警告
如果您现在运行pytest,将会得到一个 FileNotFound
错误。这是因为您尚未为测试创建控制数据。
要为您的测试创建控制数据,您必须使用 --set_test
标志和pytest。对于上面的例子,它将是:
pytest test_geometry.py::test_circle --set_test -s
(-s
在这里用于查看manim日志,以便您可以看到正在发生什么)。
如果您想查看所有控制数据帧(例如,确保您的测试正在执行您想要的操作),请使用 extract_frames.py
脚本。第一个参数是 .npz
文件的路径,第二个参数是您希望创建帧的目录。帧将命名为 frame0.png
、frame1.png
等。
python scripts/extract_frames.py tests/test_graphical_units/control_data/plot/axes.npz output
请务必在生成控制数据后立即使用 git add <您的控制数据.npz>
将其添加到git。
视频测试¶
为了测试生成的视频,我们使用装饰器 tests.utils.videos_tester.video_comparison
@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
注意
assert exit*\ code == 0, err
用于在命令运行失败的情况下。装饰器接受两个参数:json名称和视频生成路径,从 media/
目录开始。
注意这里的fixtures
tmp_path 是一个pytest fixture,用于获取临时路径。Manim将根据
--media_dir
标志在这里输出。manim_cfg_file
fixture返回指向test_scene_rendering/standard_config.cfg
的路径。这只是为了缩短代码,以防多个测试需要使用此cfg文件。simple_scenes_path
与上面相同,只是针对test_scene_rendering/simple_scene.py
您必须首先生成一个 .json
文件才能测试您的视频。为此,请使用 helpers.save_control_data_from_video
。
例如,一个检查l标志是否正常工作的测试,将首先需要从场景中渲染一个使用-l标志的视频。然后我们将测试(在这个例子中是SquareToCircle),它位于 test_scene_rendering/simple_scene.py
中。将目录更改为 tests/
,创建一个文件(例如 create\_data.py
),完成后立即删除。然后运行:
save_control_data_from_video("<path-to-video>", "SquareToCircleWithlFlag.json")
运行此命令将保存 control_data/videos_data/SquareToCircleWithlFlag.json
,其内容如下:
{
"name": "SquareToCircleWithlFlag",
"config": {
"codec_name": "h264",
"width": 854,
"height": 480,
"avg_frame_rate": "15/1",
"duration": "1.000000",
"nb_frames": "15"
}
}
如果您有任何问题,请随时在Discord、您的拉取请求中或在issue中提出。