Qt Test 概述
Qt Test 是一个用于对基于 Qt 的应用程序和库进行单元测试的框架。Qt Test 提供了单元测试框架中常见的所有功能,以及用于测试图形用户界面的扩展功能。
Qt Test 旨在简化基于 Qt 的应用程序和库的单元测试编写:
| 功能 | 详情 |
|---|---|
| 轻量级 | Qt Test 包含约6000行代码和60个导出符号。 |
| 自包含 | Qt Test 仅需从Qt Core 模块中获取少量符号即可进行非GUI测试。 |
| 快速测试 | Qt Test 无需特殊的测试运行器;测试无需特殊注册。 |
| 数据驱动测试 | 一个测试可以使用不同的测试数据执行多次。 |
| 基本 GUI 测试 | Qt Test 提供鼠标和键盘模拟功能。 |
| 基准测试 | Qt Test 支持基准测试,并提供多种测量后端。 |
| 支持 IDE | Qt Test 输出的消息可被Qt Creator 、Visual Studio和KDevelop解析。 |
| 线程安全 | 错误报告是线程安全的,且具有原子性。 |
| 类型安全性 | 广泛使用模板可防止因隐式类型转换而引入的错误。 |
| 易于扩展 | 可以轻松地将自定义类型添加到测试数据和测试输出中。 |
您可以使用Qt Creator 向导创建一个包含Qt Test的项目,并直接从Qt Creator 进行构建和运行。有关详细信息,请参阅Qt Creator :构建和运行测试。
创建测试
要创建测试,请继承QObject 并为其添加一个或多个私有槽。每个私有槽都是测试中的一个测试函数。可使用QTest::qExec() 来执行测试对象中的所有测试函数。
此外,您可以定义以下不被视为测试函数的私有槽。若存在这些槽,它们将由测试框架执行,并可用于初始化或清理整个测试或当前的测试函数。
initTestCase()将在第一个测试函数执行之前被调用。initTestCase_data()将被调用以创建全局测试数据表。cleanupTestCase()将在最后一个测试函数执行完毕后被调用。init()将在每个测试函数执行之前被调用。cleanup()将在每个测试函数执行后被调用。
请使用initTestCase() 来准备测试。每个测试都应将系统恢复到可用的状态,以便能够反复运行。清理操作应在cleanupTestCase() 中处理,这样即使测试失败,这些操作也会被执行。
请使用 `init() ` 进行测试准备。每个测试函数都应将系统恢复至可用的状态,以便能够重复运行。清理操作应在 `cleanup()` 中处理,这样即使测试函数失败并提前退出,清理操作仍会执行。
此外,您还可以使用 RAII(资源获取即初始化),在析构函数中调用清理操作,以确保当测试函数返回且对象超出作用域时,清理操作能够执行。
如果initTestCase() 失败,则不会执行任何测试函数。如果init() 失败,则不会执行后续的测试函数,测试将跳转至下一个测试函数。
示例:
classMyFirstTest:publicQObject
{
Q_OBJECT
private:
boolmyCondition()
{
return true;
}
private slots:
voidinitTestCase()
{
qDebug("Called before everything else.");
}
voidmyFirstTest()
{
QVERIFY(true);// 验证条件是否成立
QCOMPARE(1, 1);// 比较两个值
}
voidmySecondTest()
{
QVERIFY(myCondition());
QVERIFY(1 != 2);
}
voidcleanupTestCase()
{
qDebug("Called after myFirstTest and mySecondTest.");
}
};最后,如果测试类有一个静态公共的void initMain() 方法,那么在实例化QApplication 对象之前,QTEST_MAIN 宏会调用该方法。此功能是在5.14版本中添加的。
更多示例,请参阅《Qt Test 教程》。
延长测试函数超时时间
QtTest 会限制每个测试的运行时间,以捕获无限循环及类似的错误。默认情况下,任何测试函数调用在五分钟后都会被中断。对于数据驱动测试,此规则适用于每个具有不同数据标签的调用。 可以通过将QTEST_FUNCTION_TIMEOUT 环境变量设置为单次调用可接受的最大毫秒数来配置此超时。如果测试耗时超过配置的超时时间,则会被中断,并调用qFatal() 。因此,测试将默认中止,如同发生崩溃一样。
要在 Linux 或 macOS 上通过命令行设置QTEST_FUNCTION_TIMEOUT ,请输入:
QTEST_FUNCTION_TIMEOUT=900000
export QTEST_FUNCTION_TIMEOUT在 Windows 上:
SET QTEST_FUNCTION_TIMEOUT=900000然后在此环境中运行测试。
或者,您也可以在测试代码中通过编程方式设置环境变量,例如在测试类的initMain()特殊方法中调用:
qputenv("QTEST_FUNCTION_TIMEOUT", "900000");要计算合适的超时值,请观察测试通常需要多长时间,并确定在不被视为问题迹象的情况下,它最多可以多花多少时间。 将该较长时间转换为毫秒,即可得到超时值。例如,如果您认为某项测试通常需要几分钟,但在性能较差的机器上最多可接受二十分钟,则将20 * 60 * 1000 = 1200000 相乘,并将环境变量设置为1200000 ,而不是上文中的900000 。
构建测试
您可以构建一个可执行文件,其中包含一个测试类,该类通常用于测试生产代码中的一个类。不过,通常您会希望通过运行一条命令来测试项目中的多个类。
请参阅《编写单元测试》以获取分步说明。
使用 CMake 和 CTest 进行构建
您可以使用 CMake 和CTest来创建测试。CTest 允许您根据与测试名称匹配的正则表达式来包含或排除测试。您还可以为测试应用 `LABELS ` 属性,CTest 随后将根据这些标签来包含或排除测试。当在命令行上调用 `test ` 目标时,所有带有标签的目标都将被运行。
注意:在 Android平台上 ,如果仅连接了一台设备或模拟器,测试将在该设备上运行。如果连接了多台设备,请将环境变量ANDROID_DEVICE_SERIAL 设置为希望在该设备上运行测试的设备的ADB 序列号。
CMake 还有其他一些优势。例如,使用 CDash 几乎无需任何操作,即可将测试运行结果发布到 Web 服务器上。
CTest 可适配各种截然不同的单元测试框架,并且开箱即用,支持QTest 。
以下是一个 CMakeLists.txt 文件的示例,其中指定了项目名称和所用语言(此处为mytest和 C++)、构建测试所需的 Qt 模块(Qt Test ),以及测试中包含的文件(tst_mytest.cpp)。
project(mytest LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Test)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
enable_testing(true)
qt_add_executable(mytest tst_mytest.cpp)
add_test(NAME mytest COMMAND mytest)
target_link_libraries(mytest PRIVATE Qt::Test)有关可用选项的更多信息,请参阅《使用 CMake 进行构建》。
使用 qmake 构建
如果您使用qmake 作为构建工具,只需在项目文件中添加以下内容:
QT += testlib如果您希望通过make check 运行测试,请添加以下额外一行代码:
CONFIG += testcase若要防止测试被安装到目标系统上,请添加以下额外一行:
CONFIG += no_testcase_installs有关make check 的更多信息,请参阅qmake 手册。
使用其他工具进行构建
如果您使用的是其他构建工具,请确保将Qt Test 头文件的路径添加到您的包含路径中(通常位于Qt安装目录下的include/QtTest )。如果您使用的是Qt的发布版,请将您的测试程序链接到QtTest 库。对于调试版,请使用QtTest_debug 。
Qt Test 命令行参数
语法
执行自动测试的语法采用以下简单形式:
testname [options] [testfunctions[:testdata]]...将 `testname ` 替换为您的可执行文件名称。`testfunctions ` 可以包含要执行的测试函数名称。如果未传递任何 `testfunctions `,则运行所有测试。如果您在参数后附加 `testdata` 中某条目的名称,则该测试函数将仅使用该测试数据进行运行。
例如:
/myTestDirectory$ testQString toUpper运行名为toUpper 的测试函数,并使用所有可用的测试数据。
/myTestDirectory$ testQString toUpper toInt:zero使用所有可用测试数据运行名为toUpper 的测试函数,并使用名为zero 的测试数据行运行名为toInt 的测试函数(如果指定的测试数据不存在,则相关测试将失败,并报告可用的数据标签)。
/myTestDirectory$ testMyWidget -vs -eventdelay 500运行testMyWidget 函数测试,输出每个信号发射,并在每次模拟鼠标/键盘事件后等待500毫秒。
选项
日志记录选项
以下命令行选项用于确定测试结果的报告方式:
-ofilename,format
Writes output to the specified file, in the specified format (one oftxt,csv,junitxml,xml,lightxml,teamcityortap). Use the special filename-(hyphen) to log to standard output.-o文件名
将输出写入指定文件。-
-txt
以纯文本形式输出结果。 -
-csv
以逗号分隔值(CSV)格式输出结果,便于导入电子表格。此模式仅适用于基准测试,因为它会隐藏常规的通过/失败提示。 -
-junitxml
以JUnit XML文档格式输出结果。 -
-xml
以 XML 文档格式输出结果。 -
-lightxml
以 XML 标签流格式输出结果。 -
-teamcity
以TeamCity格式输出结果。 -
-tap
以Test Anything Protocol(TAP) 格式输出结果。
-o 选项的第一个版本可以重复使用,以便以多种格式记录测试结果,但该选项至多只能有一个实例将测试结果记录到标准输出中。
如果使用了-o 选项的第一种形式,则不应同时使用-o 选项的第二种形式,也不应使用-txt 、-xml 、-lightxml 、-teamcity 、-junitxml 或-tap 选项。
如果未使用-o 选项的任何版本,测试结果将输出到标准输出。如果未使用任何格式选项,测试结果将以纯文本形式输出。
测试日志详细信息选项
以下命令行选项用于控制测试日志中报告的详细程度:
-
-silent
静默输出;仅显示致命错误、测试失败和最少的状态消息。 -v1
详细输出;显示每次进入测试函数的时间。(此选项仅影响纯文本输出。)-v2
Extended verbose output; shows each QCOMPARE() and QVERIFY(). (This option affects all output formats and implies-v1for plain text output.)-vs
显示所有发出的信号以及由这些信号触发的插槽调用。(此选项影响所有输出格式。)
测试选项
以下命令行选项会影响测试的运行方式:
-
-functions
输出测试中所有可用的测试函数,然后退出。 -datatags
输出测试中所有可用的数据标签。全局数据标签前缀为“__global__”。-eventdelayms
If no delay is specified for keyboard or mouse simulation (QTest::keyClick(), QTest::mouseClick() etc.), the value from this parameter (in milliseconds) is substituted.-keydelayms
与 -eventdelay 类似,但仅影响键盘模拟,不影响鼠标模拟。-
-mousedelayms
与 -eventdelay 类似,但仅影响鼠标模拟,不影响键盘模拟。 -
-maxwarningsnumber
设置要输出的警告最大数量。0 表示无限制,默认值为 2000。 -
-nocrashhandler
在 Unix 平台上禁用崩溃处理程序。在 Windows 上,它会重新启用默认处于关闭状态的“Windows 错误报告”对话框。这对于调试崩溃非常有用。 -
-repeatn
运行测试套件 n 次,或直至测试失败。有助于发现不稳定的测试。若为负数,则测试将无限循环。此功能旨在作为开发工具,且仅在纯文本日志记录器下受支持。 -
-skipblacklisted
跳过黑名单中的测试。此选项旨在通过防止黑名单中的测试虚增覆盖率统计数据,从而实现更准确的测试覆盖率测量。当不进行测试覆盖率测量时,建议执行黑名单中的测试,以发现其结果中的任何变化,例如新的崩溃或导致被列入黑名单的问题已得到解决。 -platformname
This command line argument applies to all Qt applications, but might be especially useful in the context of auto-testing. By using the "offscreen" platform plugin (-platform offscreen) it's possible to have tests that use QWidget or QWindow run without showing anything on the screen. Currently the offscreen platform plugin is only fully supported on X11.
基准测试选项
以下命令行选项用于控制基准测试:
-callgrind
使用 Callgrind 测量基准测试时间(Linux 和 macOS)。-perf
使用 Linux perf 事件来计时基准测试-tickcounter
使用 CPU 时钟计数器来计时基准测试。需要硬件支持。-eventcounter
统计基准测试期间接收到的事件数量。-minimumvaluen
设置可接受的最小测量值。-minimumtotaln
设置测试函数重复执行时可接受的最小总次数。-iterationsn
设置累积迭代次数。-mediann
设置中位数迭代次数。-vb
输出详细的基准测试信息。
其他选项
-help
输出可用的命令行参数并提供一些有用的帮助信息。
Qt Test 环境变量
您可以设置某些环境变量,以影响自动测试的执行:
QTEST_DISABLE_CORE_DUMP
将此变量设置为非零值将禁用核心转储文件的生成。QTEST_DISABLE_STACK_DUMP
将此变量设置为非零值,将阻止 Qt Test 在自动测试超时或崩溃时打印堆栈跟踪。QTEST_FATAL_FAIL
将此变量设置为非零值将导致自动测试中的任何失败立即终止整个自动测试。这对于例如通过在调试器中启动测试来调试测试中的不稳定或间歇性故障非常有用。对该变量的支持是在 Qt 6.1 中添加的。
创建基准测试
要创建基准测试,请按照创建测试的说明操作,然后在您想要进行基准测试的测试函数中添加QBENCHMARK 宏或QTest::setBenchmarkResult()。在下面的代码片段中,使用了该宏:
class MyFirstBenchmark: public QObject
{
Q_OBJECT
private slots:
void myFirstBenchmark()
{
QString string1;
QString string2;
QBENCHMARK {
string1.localeAwareCompare(string2);
}
}
};用于测量性能的测试函数应包含一个QBENCHMARK 宏或一次setBenchmarkResult() 调用。多次出现均无意义,因为每个测试函数(或在数据驱动设置中,每个数据标签)只能报告一个性能结果。
请避免修改构成(或影响)QBENCHMARK 宏主体的测试代码,以及计算传递给setBenchmarkResult() 的值的测试代码。理想情况下,连续性能结果之间的差异应仅由您正在测试的产品所做的更改引起。 测试代码的更改可能会导致对性能变化的误导性报告。如果您确实需要更改测试代码,请在提交信息中明确说明这一点。
在性能测试函数中,调用QBENCHMARK 或setBenchmarkResult() 之后,应紧接着使用QCOMPARE()、QVERIFY() 等函数进行验证。 如果测量的不是预期的代码路径,则可以将该性能结果标记为无效。性能分析工具可以利用此信息过滤掉无效的结果。例如,意外的错误情况通常会导致程序过早地退出正常执行流程,从而错误地显示出性能的显著提升。
选择测量后端
QBENCHMARK 宏内的代码将接受性能测量,并且可能会重复执行多次以获得准确的测量结果。这取决于所选的测量后端。有多种后端可供选择,可在命令行中进行选择(参见“基准测试选项”):
| 名称 | 命令行参数 | 可用性 |
|---|---|---|
| 墙时 | (默认) | 所有平台 |
| CPU 时钟计数器 | -tickcounter | Windows、macOS、Linux 以及许多类 UNIX 系统。 |
| 事件计数器 | -eventcounter | 所有平台 |
| Valgrind Callgrind | -callgrind | Linux(若已安装) |
| Linux Perf | -perf | Linux |
简而言之,墙时(walltime)始终可用,但需要多次重复测量才能获得有用的结果。时钟计数器通常可用,且能以较少的重复次数提供结果,但容易受到 CPU 频率调节问题的影响。Valgrind 提供精确的结果,但未将 I/O 等待时间纳入考量,且仅在有限的平台上可用。 事件计数在所有平台上均可用,它提供事件循环在将事件发送至相应目标之前所接收到的事件数量(这可能包括非 Qt 事件)。
Linux 性能监控解决方案仅在 Linux 上可用,并提供多种不同的计数器,可通过传递附加选项-perfcounter countername 进行选择,例如-perfcounter cache-misses 、-perfcounter branch-misses 或-perfcounter l1d-load-misses 。默认计数器为cpu-cycles 。通过运行任何基准测试可执行文件并添加选项-perfcounterlist ,即可获取完整的计数器列表。
- 使用性能计数器可能需要启用非特权应用程序的访问权限。
- 不支持高分辨率计时器的设备默认使用 1 毫秒的粒度。
有关更多基准测试示例,请参阅《Qt Test 教程》中的“编写基准测试”部分。
使用全局测试数据
您可以定义initTestCase_data() 来设置全局测试数据表。 对于全局测试数据表中的每一行,都会运行一次测试。当测试函数本身是数据驱动的时,它会针对每一行本地数据和每一行全局数据分别运行。因此,如果全局数据表中有g 行,而测试自身的数据表中有d 行,则该测试的运行次数为g 乘以d 。
全局数据是通过QFETCH_GLOBAL()宏从表中获取的。
以下是全局测试数据的典型用例:
- 在 QSql 测试中从可用数据库后端中进行选择,以便将每个测试在每个数据库上运行。
- 在所有网络测试中,分别进行启用和禁用 SSL(HTTP 与 HTTPS)以及使用代理的情况下的测试。
- 使用高精度时钟和低精度时钟测试定时器。
- 选择解析器应从QByteArray 还是QIODevice 读取数据。
例如,要测试roundTripInt_data() 提供的每个数字与initTestCase_data() 提供的每个语言环境:
void TestQLocale::roundTripInt()
{
QFETCH_GLOBAL(QLocale, locale);
QFETCH(int, number);
bool ok;
QCOMPARE(locale.toInt(locale.toString(number), &ok), number);
QVERIFY(ok);
}在测试的命令行中,您可以传入函数名称(不带 test-class-name 前缀),以仅运行该函数的测试。 如果测试类包含全局数据,或者该函数是数据驱动的,可以在冒号后追加一个数据标签,以仅运行该标签对应的数据集。若要同时指定全局标签和测试函数的特定标签,请用冒号将它们分隔,并将全局数据标签放在前面。例如
./testqlocale roundTripInt:zero将运行上述roundTripInt() 测试中的zero 测试用例(假设其TestQLocale 类已编译为可执行文件testqlocale ),并在initTestCase_data() 指定的每个语言环境中运行,而
./testqlocale roundTripInt:C则仅在 C 语言环境中运行roundTripInt() 的全部三个测试用例,并且
./testqlocale roundTripInt:C:zero仅在 C 语言环境中运行zero 的测试用例。
对要运行的测试进行如此精细的控制,可以大大简化问题的调试过程,因为您只需逐步调试那个已知会失败的测试用例。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.