添加测试¶
如果你正在为manim添加新功能,你应该为它们添加适当的测试。测试通过检查没有其他功能被破坏和/或被无意中修改,防止manim在每次更改时出现问题。
警告
完整的测试套件需要Cairo 1.18才能运行所有测试。
然而,Cairo 1.18可能无法从您的包管理器(如apt)中获得,
而且您很可能安装的是较旧的版本,例如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中,使用git add 。
视频测试¶
为了测试生成的视频,我们使用装饰器
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/目录开始。
注意这里的夹具:
tmp_path 是一个 pytest 夹具,用于获取临时路径。根据标志
--media_dir,Manim 将在此处输出。manim_cfg_file夹具返回指向test_scene_rendering/standard_config.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、您的拉取请求或问题中提问。