Qt Test概览

Qt Test是一个基于应用程序和类库的单元测试框架. Qt Test提供单元测试框架中常见的所有功能, 也可以测试图形用户界面.

Q在基于Qt应用程序和类库的单元测试时, Qt Test很容易使用:

FeatureDetails
LightweightQt Test consists of about 6000 lines of code and 60 exported symbols.
Self-containedQt Test requires only a few symbols from the Qt Core module for non-gui testing.
Rapid testingQt Test needs no special test-runners; no special registration for tests.
Data-driven testingA test can be executed multiple times with different test data.
Basic GUI testingQt Test offers functionality for mouse and keyboard simulation.
BenchmarkingQt Test supports benchmarking and provides several measurement back-ends.
IDE friendlyQt Test outputs messages that can be interpreted by Visual Studio and KDevelop.
Thread-safetyThe error reporting is thread safe and atomic.
Type-safetyExtensive use of templates prevent errors introduced by implicit type casting.
Easily extendableCustom 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), 可以在不显示任何内容的情况下运行使用QWidgetQWindow的测试. 目前只有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宏中的代码将被测量, 并且可能重复几次, 以便获得准确的测量结果. 这取决于所选的测量后端. 你可以在命令行上使用下面列出的几个后端:

NameCommand-line ArgumentAvailability
Walltime(default)All platforms
CPU tick counter-tickcounterWindows, macOS, Linux, many UNIX-like systems.
Event Counter-eventcounterAll platforms
Valgrind Callgrind-callgrindLinux (if installed)
Linux Perf-perfLinux

简而言之, 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.