Twister 黑盒测试
本指南介绍测试文件结构,帮助读者理解现有文件并编写新测试。开发者应修复因自己的改动而失败的测试,并在引入新功能时添加测试,因此所有 Twister 开发者都应了解这些内容。
基础
Twister 黑盒测试使用 Python 和 pytest 库编写,详见 此处。辅助测试数据保留原始格式。测试和数据全部位于 scripts/tests/twister_blackbox,名称以 test_ 开头。
黑盒测试不应依赖 Twister 内部代码,而应像用户一样调用 Twister 并检查结果。
测试文件示例
1#!/usr/bin/env python3
2# Copyright (c) 2024 Intel Corporation
3#
4# SPDX-License-Identifier: Apache-2.0
5
6import importlib
7import json
8import os
9import sys
10from unittest import mock
11
12import pytest
13from conftest import TEST_DATA, ZEPHYR_BASE, test_filename_mock
14from twisterlib.testplan import TestPlan
15
16
17class TestDummy:
18 TESTDATA_X = [
19 ("smoke", 5),
20 ("acceptance", 6),
21 ]
22
23 @classmethod
24 def setup_class(cls):
25 apath = os.path.join(ZEPHYR_BASE, "scripts", "twister")
26 cls.loader = importlib.machinery.SourceFileLoader("__main__", apath)
27 cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
28 cls.twister_module = importlib.util.module_from_spec(cls.spec)
29
30 @classmethod
31 def teardown_class(cls):
32 pass
33
34 @pytest.mark.parametrize(
35 "level, expected_tests", TESTDATA_X, ids=["smoke", "acceptance"]
36 )
37 @mock.patch.object(TestPlan, "TEST_DEFINITION_FILENAME", test_filename_mock)
38 def test_level(self, capfd, out_path, level, expected_tests):
39 # Select platforms used for the tests
40 test_platforms = ["qemu_x86", "frdm_k64f"]
41 # Select test root
42 path = os.path.join(TEST_DATA, "tests")
43 config_path = os.path.join(TEST_DATA, "test_config.yaml")
44
45 # Set flags for our Twister command as a list of strs
46 args = (
47 # Flags related to the generic test setup:
48 # * Control the level of detail in stdout/err
49 # * Establish the output directory
50 # * Select Zephyr tests to use
51 # * Control whether to only build or build and run aforementioned tests
52 ["-i", "--outdir", out_path, "-T", path, "-y"]
53 # Flags under test
54 + ["--level", level]
55 # Flags required for the test
56 + ["--test-config", config_path]
57 # Flags related to platform selection
58 + [
59 val
60 for pair in zip(["-p"] * len(test_platforms), test_platforms, strict=False)
61 for val in pair
62 ]
63 )
64
65 # First, provide the args variable as our Twister command line arguments.
66 # Then, catch the exit code in the sys_exit variable.
67 with mock.patch.object(sys, "argv", [sys.argv[0]] + args), pytest.raises(
68 SystemExit
69 ) as sys_exit:
70 # Execute the Twister call itself.
71 self.loader.exec_module(self.twister_module)
72
73 # Check whether the Twister call succeeded
74 assert str(sys_exit.value) == "0"
75
76 # Access to the test file output
77 with open(os.path.join(out_path, "testplan.json")) as f:
78 j = json.load(f)
79 filtered_j = [
80 (ts["platform"], ts["name"], tc["identifier"])
81 for ts in j["testsuites"]
82 for tc in ts["testcases"]
83 if "reason" not in tc
84 ]
85
86 # Read stdout and stderr to out and err variables respectively
87 out, err = capfd.readouterr()
88 # Rewrite the captured buffers to stdout and stderr so the user can still read them
89 sys.stdout.write(out)
90 sys.stderr.write(err)
91
92 # Test-relevant checks
93 assert expected_tests == len(filtered_j)
与命令行比较
上面的测试运行以下命令:
twister -i --outdir $OUTDIR -T $TEST_DATA/tests -y --level $LEVEL
--test-config $TEST_DATA/test_config.yaml -p qemu_x86 -p frdm_k64f
它假定命令行环境已执行 zephyr-env.sh 或 zephyr-env.cmd。
借助 importlib 的 exec_module() [1],此测试可提供通常运行 Twister 时的全部输出。通过 args 变量 [2] 可方便地设置调用标志,并在 out 和 err 变量中检查标准输出和标准错误。
除标准输出外,还可检查通常位于 twister-out 目录中的文件输出。多数情况下会结合 out_path fixture 和 --outdir 标志(第 52 行),将测试生成的文件保存在临时目录。黑盒测试常读取 testplan.json、twister.xml 和 twister.log。
其他功能
装饰器
@pytest.mark.usefixtures('clear_log')允许使用
conftest.py中的clear_logfixture。未来此 fixture 将改为autouse,届时可移除此装饰器。
@pytest.mark.parametrize('level, expected_tests', TESTDATA_X, ids=['smoke', 'acceptance'])这是
pytest测试参数化示例,详见 here。TESTDATA 通常声明为类字段。
@mock.patch.object(TestPlan, 'TEST_DEFINITION_FILENAME', test_filename_mock)此装饰器允许只使用
test_data中定义的测试,忽略tests目录中的 Zephyr 用例。注意,所有 test_data 测试使用的文件名是test_data.yaml,不是testcase.yaml!test_data中的数据及mock库的更多说明见 here。
Fixture
黑盒测试使用 pytest 的 fixture,更多介绍见 here。
添加自己的 fixture 前,应考虑它只用于一个测试文件还是多个文件。
若用于多个文件,在 scripts/tests/twister_blackbox/conftest.py 中创建。
scripts/tests/twister_blackbox/conftest.py 已有一些 fixture,可作为参考。
若只用于一个文件,在该文件中声明。
也可考虑改用类字段,参见 TESTDATA 示例。
如何……
在同一测试中多次调用 Twister?
有些测试需要先运行一次 Twister。例如,--test-only 通常与前一次 --build-only 调用配合使用。该如何处理?
若直接调用两次 importlib 的 exec_module,会导致日志重复。twister.log 中每行会出现两次,调用三次则出现三次,而非覆盖日志或向末尾追加。
原因是 Twister 文件使用了模块级日志器变量,重新执行模块会使日志器拥有多个处理器。
为解决此问题,应在两次调用之间执行:
capfd.readouterr() # To remove output from the buffer
# Note that if you want output from all runs after each other,
# skip this line.
clear_log_in_test() # To remove log duplication
脚注