Qt Test概览
Qt Test是一个基于应用程序和类库的单元测试框架. Qt Test提供单元测试框架中常见的所有功能, 也可以测试图形用户界面.
Q在基于Qt应用程序和类库的单元测试时, Qt Test很容易使用:
Feature | Details |
---|---|
Lightweight | Qt Test consists of about 6000 lines of code and 60 exported symbols. |
Self-contained | Qt Test requires only a few symbols from the Qt Core module for non-gui testing. |
Rapid testing | Qt Test needs no special test-runners; no special registration for tests. |
Data-driven testing | A test can be executed multiple times with different test data. |
Basic GUI testing | Qt Test offers functionality for mouse and keyboard simulation. |
Benchmarking | Qt Test supports benchmarking and provides several measurement back-ends. |
IDE friendly | Qt Test outputs messages that can be interpreted by Visual Studio and KDevelop. |
Thread-safety | The error reporting is thread safe and atomic. |
Type-safety | Extensive use of templates prevent errors introduced by implicit type casting. |
Easily extendable | Custom types can easily be added to the test data and test output. |
Creating a Test
创建单元测试时, 你需要创建一个QObject的子类, 并添加一个或多个私有槽函数. 每一个私有槽函数是一个测试函数. 调用QTest::qExec()执行所有的测试函数.
此外, 你可以定义下列私有槽函数, 这些函数不是测试函数. 它们被测试框架调用, 用于初始化和清理所有的测试函数.
initTestCase()
第一个测试函数调用前调用.initTestCase_data()
创建一个全局的测试数据.cleanupTestCase()
最后一个测试函数调用后调用.init()
每个测试函数调用前调用.cleanup()
每个测试函数调用后调用.
调用initTestCase()
完成准备工作. 每个测试都应该使系统处于可用状态, 这样它就可以重复运行. 清理操作放在cleanupTestCase()
中, 即使测试失败, 清理操作也会被执行.
调用init()
为测试函数准备工作. 每个测试函数都应该使系统处于可用状态, 这样它就可以重复运行. 清理操作放在cleanup()
中, 即使测试函数失败, 清理操作也会被执行. 或者, 你可以使用RAII(资源获取时初始化), 在析构函数中执行清理操作, 确保测试函数返回和对象不在作用域范围时执行清理操作.
如果initTestCase()
执行失败, 则不会执行任何测试函数. 如果init()
执行失败, 则随后的测试函数不会执行. 测试框架执行下一个测试函数.
最后, 如果测试类有一个公用的静态函数void initMain()
函数, 则它会在QApplication
对象初始化前, 通过QTEST_MAIN
宏调用.
例如, 你可以在这个函数中设置应用程序属性, 如Qt::AA_DisableHighDpiScaling
. 这个功能在5.14加入.
例如:
class MyFirstTest: public QObject { Q_OBJECT private slots: void initTestCase() { qDebug("called before everything else"); } void myFirstTest() { QVERIFY(1 == 1); } void mySecondTest() { QVERIFY(1 != 2); } void cleanupTestCase() { qDebug("called after myFirstTest and mySecondTest"); } };
更多示例参见 Qt Test Tutorial.
Building a Test
如果你使用qmake
作为构建工具, 你需要在pro文件中加入如下代码:
QT += testlib
如果你想通过make check
运行测试, 你需要在pro文件中加入如下代码:
CONFIG += testcase
有关make check
的更多信息, 参见 qmake manual.
如果你使用其他构建工具, 你必须确保Qt Test的头文件(位于Qt的安装目录下的include/QtTest
)包含在你的头文件目录中. 如果你使用Qt的发布版本, 将你的测试链接到QtTest
库. 采用QtTest_debug
构建调试版本.
逐步解释参见 Writing a Unit Test.
Qt测试命令行参数
语法
执行自动测试的语法如下:
testname [options] [testfunctions[:testdata]]...
testname
是你的可执行程序名称. testfunctions
可以包含执行的测试函数. 如果不传递 testfunctions
, 执行所有的测试函数. 如果你增加 testdata
, 测试函数将仅使用testdata运行.
例如:
/myTestDirectory$ testQString toUpper
使用所有可用测试数据运行toUpper
测试函数.
/myTestDirectory$ testQString toUpper toInt:zero
使用所有可用测试数据运行toUpper
测试函数, 使用测试数据zero
运行toInt
测试函数(如果指定的测试数据不存在, 相关测试将失败).
/myTestDirectory$ testMyWidget -vs -eventdelay 500
运行testMyWidget
测试函数, 输出每次发射的信号, 并在每次模拟鼠标/键盘事件后等待500毫秒.
选项
日志操作
下面命令行选项决定报告哪种测试结果:
-o
filename,format
日志采用指定格式 (txt
,xml
,lightxml
,xunitxml
,tap
之一), 输出到指定文件. 这个指定文件-
可能是标准输出.-o
filename
输出到指定文件.-txt
结果输出到文本文件.-xml
结果输出到xml文件.-lightxml
结果作为一个XML标签的数据.-xunitxml
结果输出到XUnit XML文档.-csv
结果输出到comma-separaed values(CSV)文档. 此模式仅适用于基准测试, 因为它会抑制正常的通过/失败消息.-teamcity
输出结果是TeamCity格式.-tap
输出结果是Test Anything Protocol(TAP)格式.
为了以多种格式记录测试结果, 可以重复-o
选项的第一个版本, 但该选项的一个实例不能将测试结果记录到标准输出中.
如果使用了-o
选项的第一个版本, 则既不应使用-o
选项的第二个版本, 也不应使用-txt
, -xml
, -lightxml
, -teamcity
or -xunitxml
或-tap
选项.
如果两个版本的-o
选项都没有使用, 测试结果将记录到标准输出中. 如果没有使用格式选项, 测试结果将以纯文本形式记录.
测试日志详细选项
下述命令行选项控制测试日志的详细程度:
-silent
静默输出; 仅显示错误, 测试失败和少量状态信息.-v1
冗余输出; 显示执行的每个测试函数.(这个选项仅影响文本输出)-v2
扩展的冗余输出; 显示每个QCOMPARE()和QVERIFY().(这个选项影响所有的输出格式, 对于文本输出等价于-v1
)-vs
显示所有发送信号和这个信号相关的槽函数调用.(这个选项影响所有的输出格式.)
测试选项
下面命令行选项影响测试如何运行:
-functions
输出所有可用的测试函数, 然后退出.-datatags
输出所有可用的数据标签. 全局的数据标签前缀是'__global__'.-eventdelay
ms
如果没有为键盘或鼠标事件(QTest::keyClick(), QTest::mouseClick()等)指定延时, 则使用这个值(单位是ms)替代.-keydelay
ms
类似-eventdelay, 但是仅影响键盘事件, 不影响鼠标事件.-mousedelay
ms
类似-eventdelay, 但是仅影响鼠标事件, 不影响键盘事件.-maxwarnings
number
设置警告的最大输出数量. 0: 无限制, 默认是2000.-nocrashhandler
在Unix系统上, 禁用崩溃处理程序. 在Windows系统上, 重新启动默认关闭的Windows错误报告对话框. 这对于调试崩溃非常有用.-platform
name
这个命令行参数适用于所有Qt应用程序, 但在自动测试的上下文中可能特别有用. 通过使用"离屏"平台插件(-platform offscreen), 可以在不显示任何内容的情况下运行使用QWidget或QWindow的测试. 目前只有X11完全支持离屏平台插件.
基准选项
下面命令行选项控制基准测试:
-callgrind
使用Callgrind时间基准(仅Linux支持).-tickcounter
使用CPU计数器作为时间基准.-eventcounter
统计基准时间内事件数量.-minimumvalue
n
设置可接受的最小测量值.-minimumtotal
n
设置重复执行测试函数的最小可接受总数.-iterations
n
设置累积迭代次数.-median
n
设置中值迭代次数.-vb
输出详细的基准测试信息.
其他选项
-help
输出可能的命令行参数, 并提供一些有用的帮助.
创建一个基准
要创建基准测试, 请安装创建的测试说明进行操作, 然后在需要进行基准测试的测试函数中添加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()后面应该有一个使用QCOMPAR(), QVERIFY()等验证步骤. 然后, 如果测量到的代码路径不是预期的路径, 则可以将性能结果标记为无效. 性能分析工具可以使用此信息筛选出无效结果. 例如, 意外的错误情况通常会导致程序过早地从正常程序执行中退出, 从而错误地显示出性能的显著提高.
在QBENCHMARK宏中的代码将被测量, 并且可能重复几次, 以便获得准确的测量结果. 这取决于所选的测量后端. 你可以在命令行上使用下面列出的几个后端:
Name | Command-line Argument | Availability |
---|---|---|
Walltime | (default) | All platforms |
CPU tick counter | -tickcounter | Windows, macOS, Linux, many UNIX-like systems. |
Event Counter | -eventcounter | All platforms |
Valgrind Callgrind | -callgrind | Linux (if installed) |
Linux Perf | -perf | Linux |
简而言之, Walltime总是可用的, 但需要重复多次才能得到有用的结果. Tick Counter通常可用, 不需要重复多次就能得到有用的结果, 但可能容易受到CPU频率缩放问题的影响. Valgrind提供准确结果, 但是不考虑I/O等待, 且仅在部分平台可用. Event Counter在所有平台可用, 它提供事件循环在发送到目标前接收到的事件数量(这可能包含非Qt事件).
Linux性能监控解决方案仅在Linux上可用, 它提供多种计数器, 可以通过一个附加选项-perfcounter countername
(如-perfcounter cache-misses
, -perfcounter branch-misses
, -perfcounter l1d-load-misses
)选择. 默认的计数器是cpu-cycles
. 计数器的完整列表通过运行带有-perfcounterlist
选项的任何基准执行文件获取.
- 注意:
- 使用性能计数器可能需要启用对非特权应用程序的访问.
- 不支持高分辨率计时器的设备默认为1毫秒粒度.
有关基准测量示例参见 Writing a Benchmark.