编写一个 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.cpu
和 ti.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.000001
或 1.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
Simply using pytest.approx
won't work well here, since it's tolerance won't vary among different Taichi backends. 在 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 | 完整的量化功能 |
data64 | 64 位数据和算术 |
adstack | 保存自动微分中可变局部变量的历史 |
bls | BLS 存储 |
assertion | Taichi kernel 中的运行时 assert |
extfunc | 支持插入外部函数调用或后端源码 |