编写一个 Python 测试
通常,我们用 Python 编写功能测试。
- 我们将 pytest 作为 Python 测试的基础架构。
- Python 测试应添加到
tests/python/test_xxx.py
中。
例如,你刚刚添加了一个工具函数 ti.log10
。 现在想要编写一个测试,确保其能正常运行。
note
Before running the test launcher tests/run_tests.py
, you need to install the corresponding dependencies:
pip install -r requirements_test.txt
添加一个新的测试用例
Look into tests/python
, see if there is already a file suitable for your test. If not, create a new file for it. In this case, let's create a new file tests/python/test_logarithm.py
for simplicity.
Add a function, the function name must start with test_
so that pytest
could find it. e.g:
import taichi as ti
def test_log10():
pass
Add some tests that make use of ti.log10
to ensure it works well. Hint: You may pass/return values to/from Taichi-scope using 0-D fields, i.e. r[None]
.
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
Execute python tests/run_tests.py logarithm
, and the functions starting with test_
in tests/python/test_logarithm.py
will be executed.
在多个后端进行测试
The line ti.init(arch=ti.cpu)
in the test above means that it will only test on the CPU backend. In order to test against multiple backends, please use the @ti.test
decorator, as illustrated below:
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
In this case, Taichi will execute this test case on both ti.cpu
and ti.cuda
backends.
And you may test against all available backends (depends on your system and building environment) by simply not specifying the argument:
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
进行误差比较
Sometimes the precision of math operations could be limited on certain backends such as OpenGL, e.g., ti.log10(100)
may return 2.000001
or 1.999999
in this case.
Adding tolerance with ti.approx
can be helpful to mitigate such errors on different backends, for example 2.001 == ti.approx(2)
will return True
on the OpenGL backend.
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. It'll likely fail on the OpenGL backend.
ti.approx
also correctly treats boolean types, e.g.: 2 == ti.approx(True)
.
参数化测试输入
In the test above, r[None] = 100
means that it will only test that ti.log10
works correctly for the input 100
. In order to test against different input values, you may use the @pytest.mark.parametrize
decorator:
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)
Use a comma-separated list for multiple input values:
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)
Use two separate parametrize
to test all combinations of input arguments:
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
配置
You may specify keyword arguments to ti.init()
in ti.test()
, e.g.:
@ti.test(ti.cpu, debug=True, log_level=ti.TRACE)
def test_debugging_utils():
# ... (some tests have to be done in debug mode)
is the same as:
def test_debugging_utils():
ti.init(arch=ti.cpu, debug=True, log_level=ti.TRACE)
# ... (some tests have to be done in debug mode)
在测试时排除一些后端
Some backends are not capable of executing certain tests, you may have to exclude them from the test in order to move forward:
# 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)
You may also use the extensions
keyword to exclude backends without a specific feature:
# 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)
在测试中添加扩展
If a test case depends on some extensions, you should add an argument require
in the @ti.test
decorator.
@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
Now, Taichi supports the following extensions:
名称 | 扩展详情 |
---|---|
sparse | 稀疏数据结构 |
quant_basic | 基本量化运算 |
quant | 完整的量化功能 |
data64 | 64 位数据和算术 |
adstack | 保存自动微分中可变局部变量的历史 |
bls | BLS 存储 |
assertion | Taichi kernel 中的运行时 assert |
extfunc | 支持插入外部函数调用或后端源码 |