Qt Test 概述

Qt Test 是一个用于对基于 Qt 的应用程序和库进行单元测试的框架。 提供了单元测试框架中常见的所有功能,以及用于测试图形用户界面的扩展功能。Qt Test

Qt Test 该框架旨在简化基于 Qt 的应用程序和库的单元测试编写工作:

功能详细信息
轻量级Qt Test 由大约 6000 行代码和 60 个导出符号组成。
自包含Qt Test 只需 模块中的几个符号即可进行非 gui 测试。Qt Core
快速测试Qt Test 无需专门的测试运行程序;无需专门的测试注册。
数据驱动测试可使用不同的测试数据多次执行测试。
基本图形用户界面测试Qt Test 提供鼠标和键盘模拟功能。
基准测试Qt Test 支持基准测试,并提供多个测量后端。
集成开发环境友好Qt Test 输出可由 、Visual Studio 和 KDevelop 解释的信息。Qt Creator
线程安全错误报告是线程安全和原子的。
类型安全广泛使用模板,防止隐式类型转换带来的错误。
易于扩展自定义类型可轻松添加到测试数据和测试输出中。

您可以使用Qt Creator 向导创建包含 Qt Test 的项目,并直接从Qt Creator 构建和运行它们。欲了解更多信息,请参阅Qt Creator 构建和运行测试

创建测试

要创建一个测试,请将QObject 子类,并添加一个或多个私有槽。每个私有槽都是测试中的一个测试函数。QTest::qExec() 可用于执行测试对象中的所有测试函数。

此外,您还可以定义以下被视为测试函数的私有槽。它们出现时,将由测试框架执行,并可用于初始化和清理整个测试或当前测试函数。

  • initTestCase() 将在第一个测试函数执行前被调用。
  • initTestCase_data() 将在创建全局测试数据表时调用。
  • cleanupTestCase() 在执行最后一个测试函数后调用。
  • init() 在执行每个测试功能之前调用。
  • cleanup() 在每个测试函数执行后调用。

使用initTestCase() 准备测试。每个测试都应使系统处于可用状态,以便重复运行。清理操作应在cleanupTestCase() 中处理,这样即使测试失败也能运行。

使用init() 准备测试功能。每个测试功能都应使系统处于可用状态,以便重复运行。清理操作应在cleanup() 中处理,这样即使测试功能失败并提前退出,它们也会被运行。

或者,也可以使用 RAII(资源获取即初始化),在析构函数中调用清理操作,确保它们在测试函数返回和对象移出作用域时发生。

如果initTestCase() 失败,则不会执行任何测试函数。如果init() 失败,则不会执行下面的测试函数,测试将进入下一个测试函数。

示例

MyFirstTest:公共QObject
{ Q_OBJECTprivate: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 Tutorial

增加测试函数超时

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 分钟,比如在一台慢速机器上,那么就乘以20 * 60 * 1000 = 1200000 ,并将环境变量设置为1200000 ,而不是上面的900000

构建测试

您可以构建一个包含一个测试类的可执行文件,该测试类通常测试一类生产代码。不过,通常情况下,您希望通过运行一条命令来测试一个项目中的多个类。

具体步骤请参见编写单元测试

使用 CMake 和 CTest 构建

您可以使用CMake 和 CTest来创建测试。CTest可让你根据与测试名称匹配的正则表达式来包含或排除测试。你还可以对测试应用LABELS 属性,然后 CTest 可以根据这些标签包含或排除测试。在命令行中调用test target 时,将运行所有贴有标签的目标。

注意: 在安卓系统上,如果只连接了一个设备或模拟器,测试将在该设备上运行。如果连接的设备不止一个,请将环境变量ANDROID_DEVICE_SERIAL 设置为要运行测试的设备的ADB 序列号

CMake 还有其他一些优点。例如,使用 CDash 几乎不费吹灰之力就能在网络服务器上发布测试运行结果。

CTest 可扩展到各种不同的单元测试框架,并可与QTest.NET Framework 一起使用。

下面是 CMakeLists.txt 文件的示例,其中指定了项目名称和使用的语言(此处为mytest和 C++)、构建测试所需的 Qt 模块(Qt5Test)以及测试中包含的文件(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 毫秒。

选项

日志选项

以下命令行选项决定如何报告测试结果:

  • -o 文件名,格式
    Writes output to the specified file, in the specified format (one of txt, csv, junitxml, xml, lightxml, teamcity or tap). Use the special filename - (hyphen) to log to standard output.
  • -o filename
    将输出写入指定文件。
  • -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 -v1 for plain text output.)
    • -vs
      显示所有发出的信号以及这些信号导致的槽调用。(此选项影响所有输出格式)

    测试选项

    以下命令行选项会影响测试的运行方式:

    • -functions
    • 输出测试中可用的所有测试函数,然后退出。
    • -datatags
      输出测试中可用的所有数据标记。全局数据标记前面有"__global__"。
    • -eventdelay ms
      If no delay is specified for keyboard or mouse simulation (QTest::keyClick(), QTest::mouseClick() etc.), the value from this parameter (in milliseconds) is substituted.
    • -keydelay ms
      与 -eventdelay 类似,但只影响键盘模拟,不
    • 影响
    • 鼠标模拟。
    • -mousedelay ms
      与 -eventdelay 类似,但只影响鼠标模拟,不影响键盘模拟。
    • -maxwarnings number
      设置输出警告的最大数量。0 表示无限制,默认为 2000。
    • -nocrashhandler
      在 Unix 平台上禁用崩溃处理程序。在 Windows 平台上,它会重新启用默认关闭的 Windows 错误报告对话框。这对调试崩溃很有用。
    • -repeat n
      运行测试套件 n 次或直到测试失败。这对发现错误测试很有用。如果失败,测试将永远重复。
    • -skipblacklisted
      跳过黑名单测试。该选项的目的是防止黑名单测试夸大覆盖率统计数据,从而更准确地测量测试覆盖率。在不测量测试覆盖率时,建议执行黑名单测试,以显示其结果的任何变化,如新的崩溃或导致黑名单的问题得到解决。
    • -platform 名称
      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)。
    • -tickcounter
      使用 CPU tick 计数器为基准测试计时。
    • -eventcounter
      对基准测试期间收到的事件进行计数。
    • -minimumvalue n
      设置可接受的最小测量值。
    • -minimumtotal n
      设置重复执行测试函数的最小可接受总数。
    • -iterations n
      设置累积迭代次数。
    • -median n
      设置中值迭代次数。
    • -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() 的值的测试代码。连续性能结果的差异最好仅由测试产品的更改引起。更改测试代码可能会导致误导性的性能变化报告。如果确实需要更改测试代码,请在提交信息中明确说明。

    在性能测试功能中,QBENCHMARKsetBenchmarkResult() 之后应使用QCOMPARE() 和QVERIFY() 等进行验证。如果测得的代码路径与预期路径不同,则可以将性能结果标记为无效。性能分析工具可利用此信息过滤掉无效结果。例如,意外错误条件通常会导致程序从正常程序执行中提前退出,从而错误地显示出性能的大幅提升。

    选择测量后端

    将对 QBENCHMARK 宏内的代码进行测量,为了获得准确的测量结果,可能还会重复多次。这取决于所选的测量后端。有多个后端可供选择。您可以在命令行中进行选择:

    名称命令行参数可用性
    挂起时间默认所有平台
    CPU 滴答计数器-tickcounterWindows、macOS、Linux 和许多类 UNIX 系统。
    事件计数器-eventcounter所有平台
    Valgrind Callgrind-callgrindLinux(如已安装)
    Linux Perf-perfLinux

    简而言之,walltime 始终可用,但需要多次重复才能获得有用的结果。Tick 计数器通常可用,并能以较少的重复次数提供结果,但容易受到 CPU 频率缩放问题的影响。Valgrind 可以提供精确的结果,但不考虑 I/O 等待,而且只适用于有限的平台。事件计数在所有平台上都可用,它提供事件循环在发送到相应目标(可能包括非 Qt 事件)之前接收到的事件数量。

    Linux 性能监控解决方案仅适用于 Linux,并提供许多不同的计数器,可通过附加选项-perfcounter countername 进行选择,如-perfcounter cache-misses-perfcounter branch-misses-perfcounter l1d-load-misses 。默认计数器为cpu-cycles 。使用-perfcounterlist 选项运行任何基准可执行文件,都可获得完整的计数器列表。

    • 使用性能计数器可能需要启用非特权应用程序的访问权限。
    • 不支持高分辨率计时器的设备默认为一毫秒粒度。

    有关更多基准测试示例,请参阅Qt Test Tutorial 中的编写基准测试。

    使用全局测试数据

    您可以定义initTestCase_data() 来设置全局测试数据表。每个测试都要针对全局测试数据表中的每一行运行一次。如果测试功能本身是数据驱动的,则会针对每一行本地数据和每一行全局数据运行。因此,如果全局数据表中有g 行,而测试本身的数据表中有d 行,那么该测试的运行次数就是g 乘以d

    全局数据使用QFETCH_GLOBAL() 宏从表中获取。

    以下是全局测试数据的典型用例:

    • 在 QSql 测试中选择可用的数据库后端,以便针对每个数据库运行每个测试。
    • 使用或不使用 SSL(HTTP 与 HTTPS)和代理进行所有网络测试。
    • 使用高精度时钟和粗时钟测试定时器。
    • 选择解析器应从QByteArray 还是QIODevice 读取数据。

    例如,用initTestCase_data() 提供的每个本地语言测试roundTripInt_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() 将在initTestCase_data() 指定的每个本地运行zero 测试用例(假设其TestQLocale 类已编译为可执行testqlocale ),而

    ./testqlocale roundTripInt:C

    将仅在 C 语言环境中运行roundTripInt() 的所有三个测试用例,而

    ./testqlocale roundTripInt:C:zero

    将仅在 C 语言环境中运行zero 测试用例。

    对要运行的测试进行如此细粒度的控制,可以大大简化问题的调试工作,因为只需一步步处理出现故障的测试用例即可。

    © 2025 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.