跳转至主要内容
Version: v1.1.0

编写一个 Python 测试

通常,我们用 Python 编写功能测试。

  • 我们将 pytest 作为 Python 测试的基础架构。
  • Python 测试应添加到 tests/python/test_xxx.py 中。

例如,你刚刚添加了一个工具函数 ti.log10。 现在想要编写一个测试,确保其能正常运行。

添加一个新的测试用例

tests/python 中查看是否已经有适合测试的文件。 如果没有,请创建一个新文件。 在这里,为简单起见,我们创建一个新文件 tests/python/test_logarithm.py

添加一个函数,函数名称必须test_ 开头,以便 pytest 可以找到它。例如:

import taichi as ti

def test_log10():
pass

添加一些用到 ti.log10 的测试,以确保其正常运行。 提示:你可以使用 0 维 field(即 r[None])向 Taichi 作用域传递值或从中返回值。

import taichi as ti

def test_log10():
ti.init(arch=ti.cpu)

r = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.log10(r[None])

r[None] = 100
foo()
assert r[None] == 2

执行 python tests/run_tests.py logarithm,随后 tests/python/test_logarithm.py 中以 test_ 开头的函数就会被执行。

在多个后端进行测试

上述代码片段中的 ti.init(arch=ti.cpu) 意味着测试仅在 CPU 后端上进行。 为了在多个后端进行测试,请使用 @ti.test 装饰器,如下所示:

import taichi as ti

# will test against both CPU and CUDA backends
@ti.test(arch=[ti.cpu, ti.cuda])
def test_log10():
r = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.log10(r[None])

r[None] = 100
foo()
assert r[None] == 2

在这种情况下,Taichi 将在 ti.cputi.cuda 后端执行此测试案例。

只要不指定参数,你也可以在所有可用的后端(取决于所用系统和构建环境)进行测试:

import taichi as ti

# will test against all backends available on your end
@ti.test()
def test_log10():
r = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.log10(r[None])

r[None] = 100
foo()
assert r[None] == 2

使用 ti.approx 进行误差比较

有时,在某些后端(例如 OpenGL)上,数学运算的精度有限。例如,在本用例中,ti.log10(100) 可能会返回 2.0000011.999999

使用 ti.approx 增加误差参数,可以在各后端上减少此类误差,例如,在 OpenGL 后端上,2.001 == ti.approx(2) 将返回 True

import taichi as ti

# will test against all backends available on your end
@ti.test()
def test_log10():
r = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.log10(r[None])

r[None] = 100
foo()
assert r[None] == ti.approx(2)
caution

仅使用 pytest.approx 的效果不佳,因为其误差参数在不同的 Taichi 后端上没有区别。 在 OpenGL 后端很可能运行失败。

ti.approx 也能正确处理布尔类型,例如:2 == ti.approx(True)

参数化测试输入

在上述测试中, r[None] = 100 意味着它只会测试 ti.log10 能否在输入为 100 时正常运行。 要测试不同的输入值,你可以使用 @pytest.mark.parametrize 装饰器。

import taichi as ti
import pytest
import math

@pytest.mark.parametrize('x', [1, 10, 100])
@ti.test()
def test_log10(x):
r = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.log10(r[None])

r[None] = x
foo()
assert r[None] == math.log10(x)

有多个输入值时,使用逗号分隔列表:

import taichi as ti
import pytest
import math

@pytest.mark.parametrize('x,y', [(1, 2), (1, 3), (2, 1)])
@ti.test()
def test_atan2(x, y):
r = ti.field(ti.f32, ())
s = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.atan2(r[None])

r[None] = x
s[None] = y
foo()
assert r[None] == math.atan2(x, y)

使用两个独立的 parametrize 来测试输入参数的所有组合

import taichi as ti
import pytest
import math

@pytest.mark.parametrize('x', [1, 2])
@pytest.mark.parametrize('y', [1, 2])
# same as: .parametrize('x,y', [(1, 1), (1, 2), (2, 1), (2, 2)])
@ti.test()
def test_atan2(x, y):
r = ti.field(ti.f32, ())
s = ti.field(ti.f32, ())

@ti.kernel
def foo():
r[None] = ti.atan2(r[None])

r[None] = x
s[None] = y
foo()
assert r[None] == math.atan2(x, y)

指定 ti.init 配置

你可以在 ti.test() 中为 ti.init() 指定关键字参数,例如:

@ti.test(ti.cpu, debug=True, log_level=ti.TRACE)
def test_debugging_utils():
# ... (some tests have to be done in debug mode)

等同于:

def test_debugging_utils():
ti.init(arch=ti.cpu, debug=True, log_level=ti.TRACE)
# ... (some tests have to be done in debug mode)

在测试时排除一些后端

一些后端无法执行某些测试,你可能需要排除它们才能推进测试:

# Run this test on all backends except for OpenGL
@ti.test(exclude=[ti.opengl])
def test_sparse_field():
# ... (some tests that requires sparse feature which is not supported by OpenGL)

你可能需要使用关键词 extensions 来排除没有特定功能的后端:

# Run this test on all backends except for OpenGL
@ti.test(extensions=[ti.extension.sparse])
def test_sparse_field():
# ... (some tests that requires sparse feature which is not supported by OpenGL)

在测试中添加扩展

如果测试用例依赖于某些扩展,你应该在 @ti.test 装饰器中添加参数 require

@ti.test(require=ti.extension.sparse)
def test_struct_for_pointer_block():
n = 16
block_size = 8

f = ti.field(dtype=ti.f32)

block = ti.root.pointer(ti.ijk, n // block_size)
block.dense(ti.ijk, block_size).place(f)

f[0, 2, 3] = 1

@ti.kernel
def count() -> int:
tot = 0
for I in ti.grouped(block):
tot += 1
return tot

assert count() == 1

目前,Taichi 支持以下扩展:

名称扩展详情
sparse稀疏数据结构
quant_basic基本量化运算
quant完整的量化功能
data6464 位数据和算术
adstack保存自动微分中可变局部变量的历史
blsBLS 存储
assertionTaichi kernel 中的运行时 assert
extfunc支持插入外部函数调用或后端源码
packed排布紧凑模式:不会为 field 采用 2 次幂缓存策略。
dynamic_index对 tensor 的动态索引支持