跳转至主要内容
Version: develop

编写一个 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完整的量化功能
data6464 位数据和算术
adstack保存自动微分中可变局部变量的历史
blsBLS 存储
assertionTaichi kernel 中的运行时 assert
extfunc支持插入外部函数调用或后端源码