Qt6 c++教程9测试&调试

9 测试&调试

调试和测试是软件开发的重要组成部分。在本章中,你将学习如何调试 Qt 项目、不同的调试技术以及 Qt 支持的调试器。调试是发现错误或不希望出现的行为的根本原因并加以解决的过程。我们还将讨论使用Qt Test框架进行单元测试。Qt Test是基于Qt的应用程序和库的单元测试框架。它具有大多数单元测试框架提供的所有功能。此外,它还支持测试图形用户界面(GUI)。本模块有助于以便捷的方式编写基于 Qt 的应用程序和库的单元测试。您还将学习使用不同图形用户界面测试工具测试图形用户界面的技巧。

具体来说,我们将讨论以下主题:

  • Qt中的调试
  • 调试策略
  • 调试C++应用程序
  • 调试Qt Quick应用程序
  • Qt中的测试
  • 与Google的 C++ 测试框架集成
  • 测试Qt Quick应用程序
  • 图形用户界面测试工具

9.1 Qt 调试

在软件开发过程中,经常会出现技术问题。为了解决这些问题,我们必须在向公众发布应用程序之前首先确定并解决所有问题,以保证质量和声誉。调试是一种定位这些潜在技术问题的技术。

Qt 支持几种不同类型的调试器。您所使用的调试器可能因您的项目所使用的平台和编译器而异。以下是 Qt 广泛使用的调试器列表:

  • GNU 符号调试器(GDB)是由 GNU 项目开发的跨平台调试器。
  • Microsoft Console Debugger (CDB) 是 Microsoft 为 Windows 开发的调试器。
  • 低级虚拟机调试器(LLDB)是由 LLVM 开发人员小组开发的跨平台调试器。
  • QML/JavaScript Debugger 是 Qt 公司提供的 QML 和 JavaScript 调试器。

如果您在 Windows 上使用 MinGW 编译器,则无需手动设置GDB,因为它通常包含在 Qt 安装中。如果您使用的是Linux等其他操作系统,则可能需要在将其链接到 Qt Creator之前手动安装。Qt Creator会自动检测 GDB 的存在,并将其添加到调试器列表中。

你也可以使用Valgrind来调试你的应用程序。你可以指定 --vgdb=yes 或 --vgdb=full 来激活 Valgrind gdbserver。你可以指定 --vgdb-error=number 来在显示一定数量的错误后激活 gdbserver。如果将该值设为 0,gdbserver 将在初始化时激活,这样就可以在程序启动前设置断点。值得注意的是,Valgrind 发行版中已包含vgdb。无需单独安装。

Windows可安装 CDB。默认情况下,Visual Studio 的内置调试器不可用。因此,您必须单独安装CDB调试器,方法是在安装 Windows SDK 时将 Windows 的调试工具选为可选组件。Qt Creator 通常会识别 CDB 的存在,并将其添加到选项下的调试器列表中。

Android 调试比在普通桌面环境中调试更具挑战性。Android开发需要不同的软件包,如JDK、Android SDK 和 Android NDK。在桌面平台上,需要使用 Android Debug Bridge(ADB)驱动程序才能进行 USB 调试。您必须在 Android 设备上启用开发者模式并接受 USB 调试才能继续。

MacOS 和 iOS 上使用的调试器是 LLDB。它默认包含在 Xcode 中。Qt Creator 会自动检测它的存在,并将其与工具包链接。如果您熟悉调试器并知道自己在做什么,也可以将非 GDB 调试器添加到您最喜欢的集成开发环境中。

调试器插件会根据机器上可用的调试器为每个软件包确定一个合适的本地调试器。你可以通过添加新调试器来克服这种偏好。如图 9.1 所示,你可以在 "选项 "菜单下的 "工具包 "设置中的 "调试器 "选项卡中找到可用的调试器:

9.2 调试策略

有不同的调试策略可以找到问题的根本原因。在尝试查找应用程序中的错误之前,彻底了解程序或程序库至关重要。如果不了解自己在做什么,就无法发现错误。只有彻底了解系统及其运行方式,才能找出应用程序中的错误。以往的经验有助于发现类似类型的错误以及解决错误。专家个人的知识决定了开发人员可以多容易地找到错误。您可以添加调试打印语句和断点来分析程序的流程。您可以进行前向分析或后向分析,以跟踪错误的位置。

调试时,可通过以下步骤找到根本原因并加以解决:

  • 确定问题。
  • 定位问题。
  • 分析问题。
  • 解决问题。
  • 修复副作用。

无论使用哪种编程语言或平台,调试应用程序时最重要的是要知道是哪段代码导致了问题。您可以通过多种方式找到有问题的代码。

如果缺陷是由质量保证团队或用户提出的,那么可以询问问题发生的时间。查看日志文件或任何错误信息。注释掉代码中可疑的部分,然后再次构建并运行应用程序,看看问题是否仍然存在。如果问题可以重现,则在找到导致问题的代码行之前,通过打印信息和注释代码行进行正向和反向分析。

还可以在内置调试器中设置断点,搜索目标功能中的变量变化。如果某个变量更新到了意外值,或者某个对象指针变成了无效指针,那么你就可以很容易地识别出来。检查安装程序中使用的所有模块,确保您和用户使用的应用程序版本号相同。如果使用的是不同的版本或不同的分支,那么请检查带有指定版本标记的分支,然后调试代码。

9.3 调试C++应用程序

QDebug类可用于将变量值打印到应用程序输出窗口。QDebug类似于标准库中的std::cout,它是Qt的一部分,支持 Qt 类,无需转换即可显示其值。

要启用调试信息,我们必须包含 QDebug 头文件。

Qt 提供了多个全局宏,用于生成不同类型的调试信息。它们可用于以下不同目的:

  • qDebug() 提供自定义调试信息。
  • qInfo() 提供信息消息。
  • qWarning() 报告警告和可恢复错误。
  • qCritical() 提供关键错误信息并报告系统错误。
  • qFatal() 在退出前提供致命错误信息。

使用qDebug()可以查看功能是否正常运行。查找完错误后,删除包含qDebug()的代码行,以避免出现不必要的控制台日志。让我们通过一个例子来看看如何使用 qDebug()在输出窗格中打印变量。创建一个示例QWidget应用程序,添加函数 setValue(int value),并在函数定义中添加以下代码:

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setValue(100);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::setValue(int value)
{
    qDebug()<<"Value is:" <<value;
}

执行后控制台会有输出:

可通过调试菜单-开始调试 启动调试:

你也可以点击行号来添加断点。点击行号设置断点。你会看到行号上出现一个红点。
接下来,按键盘上的 F5 键或单击启动调试按钮。在调试模式下运行应用程序后,你会发现第一个红点的顶部出现了一个黄色箭头:

调试器在第一个断点处停止。现在,变量及其含义和类型将显示在 Qt Creator 右侧的 Locals 和 Expression 窗口中。
这种方法可用于快速检查应用程序。要移除断点,只需再次点击红点图标或从右键上下文菜单中点击即可。

您可以在以下文档中了解更多特性及其用法:https://doc.qt.io/qt-6/debug.html

重要提示:某些防病毒程序会阻止调试器获取信息。Avira 就是这样一种反病毒软件。如果在生产 PC 上安装了 Avira,在 Windows 平台上调试器的启动可能会失败。

下一节,我们将讨论如何调试 Qt Quick 应用程序并查找 QML 文件中的问题。

9.4 调试 Qt Quick 应用程序

在上一节中,我们讨论了如何调试 C++ 代码。但你可能仍想知道如何调试用QM 编写的代码。

就像 QDebug 类一样,QML 中也有不同的控制台 API 可用于调试。它们如下:

  • 日志(Log): 用于打印一般信息。
  • Assert: 用于验证表达式。
  • Timer:用于测量调用之间的时间间隔。
  • Trace 用于打印 JavaScript 执行的堆栈跟踪。
  • Count用于查找函数的调用次数。
  • Profile: 用于剖析 QML 和 JavaScript 代码。
  • Exception 异常 用于打印错误信息。

控制台 API 提供了几个方便的函数来打印不同类型的调试信息,如 console.log()、console.debug()、console.info()、console.warn() 和 console.error()。您可以按以下方式打印带有参数值的信息:console.log("Value is:", value)

您还可以通过在 Components.onCompleted:{...} 内添加信息来检查组件的创建情况:

Components.onCompleted: {

     console.log("Component created")

}

要验证表达式是否为真,可以使用 console.assert(),例如下面的例子:

console.assert(value == 100, "Reached the maximum limit");

console.time() 和 console.timeEnd() 会记录调用之间的时间间隔。console.trace() 会打印 JavaScript 在调用阶段的堆栈跟踪。函数名、文件名、行数和列数都包含在堆栈跟踪详情中。

console.count() 会返回一段代码的当前执行次数以及一条信息。使用 console.profile()会激活QML和JavaScript分析,调用 console.profileEnd()则会停用。你可以使用 console.exception() 来打印错误信息和 JavaScript 执行的堆栈跟踪。

添加断点的方法与前一节讨论的相同,如下所示:

  • 要进入堆栈中的代码,请单击工具栏上的 "进入"(Step Into)按钮或按 F11 键。
  • 要退出代码,请按 Shift + F11。要点击断点,在方法末尾添加一个断点,然后点击 Continue(继续)。
  • 打开 QML 调试器控制台输出窗格,在当前上下文中运行 JavaScript 命令。

您可以在运行 Qt Quick 应用程序时发现问题并观察数值。它将帮助你找到导致意外行为并需要修改的代码部分。

9.5 Qt中的测试

单元测试是一种使用自动化工具测试简单应用程序、类或函数的方法。我们将讨论什么是单元测试,以及为什么要进行单元测试,然后再讨论如何使用Qt Test将单元测试纳入我们的方法中。单元测试是将应用程序分解为最小的功能单元,然后在程序框架内用实际情况测试每个单元的过程。单元是应用程序中可测试的最小组件。程序设计中的单元测试通常侧重于功能或流程。

面向对象程序设计中的单元通常是接口、类或函数。单元测试能在实施过程的早期发现问题。这包括程序员实现过程中的故障,以及单元规范中的缺陷或不完整部分。在创建过程中,单元测试是由待测单元的开发人员开发的简短代码片段。有许多单元测试工具可用于测试C++代码。让我们探讨一下Qt测试框架的优势和特点。

9.5.1 Qt中的单元测试

Qt Test是基于Qt的应用程序和库的单元测试平台。Qt Test包括传统单元测试应用程序中的所有功能,以及用于测试图形用户界面的插件。它有助于使为基于Qt的程序和库编写单元测试变得更加容易。

以前,单元测试可能需要手动完成,尤其是图形用户界面测试,但现在有一种工具可以让您编写代码来自动验证代码,这初看起来可能有悖常理,但却能正常工作。Qt Test是一个专门的测试框架,用于基于Qt的单元测试。

要使用Qt内置的单元测试模块,必须在项目文件(.pro)中添加 testlib:QT += core testlib

接下来,运行 qmake 为项目添加可用模块。为了让测试系统找到并实现它,你必须使用QTest头文件,并将测试函数声明为私有槽。QTest头包含与Qt Test相关的所有函数和语句。要使用QTest功能,只需在 C++ 文件中添加以下一行即可:#include

您应为每一种可能的情况编写测试用例,然后在每次更改基线代码时运行测试,以确保系统继续按预期运行。这是一个非常有用的工具,可确保任何程序更新都不会破坏现有功能。

让我们使用Qt Creator 的内置向导创建简单的测试应用程序。从新建项目菜单中选择自动测试项目:

测试代码 tst_testclass.cpp

#include <QtTest>

// add necessary includes here

class TestClass : public QObject
{
    Q_OBJECT

public:
    TestClass() {}
    ~TestClass(){}

private slots:
    void initTestCase(){}
    void cleanupTestCase() {}
    void test_compareStrings();
    void test_compareValues();

};


void TestClass::test_compareStrings()
{
    QString string1 = QLatin1String("Apple");
    QString string2 = QLatin1String("Orange");
    QCOMPARE(string1.localeAwareCompare(string2), 0);
}
void TestClass::test_compareValues()
{
    int a = 10;
    int b = 20;
    int result = a + b;
    QCOMPARE(result,30);
}

QTEST_APPLESS_MAIN(TestClass)

#include "tst_testclass.moc"

要执行所有测试用例,必须在文件底部添加 QTEST_MAIN() 等宏。QTEST_MAIN() 宏扩展为一个简单的 main() 方法,可运行所有测试函数。QTEST_APPLESS_MAIN() 宏适用于不使用 QApplication 对象的简单独立非图形用户界面测试。如果不需要图形用户界面,但需要事件循环,则使用 QTEST_GUILESS_MAIN():

为了使测试用例成为独立的可执行文件,我们添加了 QTEST_APPLESS_MAIN() 宏和该类的 moc 生成文件。您还可以使用其他一些宏来测试应用程序。如需了解更多信息,请访问以下链接:http://doc.qt.io/qt-6/qtest.html#macros

运行前面的示例时,您将看到如下所示的测试结果输出:

还可以从 Qt Creator菜单栏的工具-测试-运行所有测试)选项运行所有测试执行。

您还可以在左侧的项目浏览器视图中查看所有测试用例。从项目浏览器下拉菜单中选择测试。您可以在此窗口中启用或禁用某些测试用例。

您可以使用几个QTest便捷函数来模拟图形用户界面事件,如键盘或鼠标事件。让我们通过一个简单的代码片段来了解它们的用法:

QTest::keyClicks(testLineEdit, "Enter");

QCOMPARE(testLineEdit->text(), QString("Enter"));

在前面的代码中,测试代码在行编辑控件上模拟键盘文本输入事件,然后验证输入的文本。您还可以使用 QTest::mouseClick() 模拟鼠标点击事件。使用方法如下:

QTest::mouseClick(testPushBtn, Qt::LeftButton);

Qt 的测试框架在测试驱动开发(TDD)中也很有用。在 TDD 中,您首先要编写测试,然后再编写实际逻辑代码。由于没有实现,测试最初会失败。然后,在继续下一个测试之前,编写通过测试所需的最基本代码。这就是在实现必要功能之前迭代开发功能的方法。

参考资料

9.6 与Google C++测试框架集成

GoogleTest是由Google开发的测试和模拟框架。GoogleMock项目已并入GoogleTest。GoogleTest需要至少支持C++11标准的编译器。它是一个跨平台测试框架,支持 Windows、Linux 和 macOS 等主要桌面平台。它能帮助你编写更好的 C++ 测试,并具有模拟等高级功能。您可以将 Qt Test 与 GoogleTest 集成,以获得两个框架的最佳效果。如果你打算同时使用这两个测试框架的功能,那么你应该使用 GoogleTest 作为主要的测试框架,而在测试用例中,你可以使用 Qt Test 的功能。

Qt Creator 内置支持 GoogleTest。你可以在选项界面的测试部分找到 Google Test 选项卡,并设置全局 GoogleTest 偏好,如图 9.10 所示:

您可以从以下链接下载 GoogleTest 源代码:https://github.com/google/googletest。您可以从以下文档中了解更多有关功能及其使用的信息:https://google.github.io/googletest/primer.html

下载源代码后,请先构建库,然后再创建示例应用程序。您也可以在构建测试项目的同时构建统一的 GoogleTest 源代码。生成库后,请按照以下步骤运行 GoogleTest 应用程序:

本节以下内容后续补齐。

9.7 测试Qt Quick应用程序

Qt Quick Test 是为 Qt Quick 应用程序的单元测试而创建的框架。测试用例使用JavaScript编写,并使用 TestCase QML 类型。名称以test_开头的函数被识别为需要执行的测试用例。测试工具会递归搜索所需源目录中的 tst_ *.qml 文件。您可以将所有测试 .qml 文件放在一个目录下,并定义 QUICK_TEST_SOURCE_DIR。如果未定义该目录,则在执行测试时将只包含当前目录下的 .qml 文件。Qt 并不确保 Qt 快速测试模块的二进制兼容性。您必须使用相应版本的模块。

要开始执行测试用例,必须在 C++ 文件中添加 QUICK_TEST_MAIN(),如下所示:

#include <QtQuickTest>
QUICK_TEST_MAIN(testqml)

您需要添加 qmltest 模块以启用 Qt 快速测试。在 .pro 文件中添加以下代码行:

QT += qmltest
TEMPLATE = app
TARGET = tst_calculations
CONFIG += qmltestcase
SOURCES += testqml.cpp

让我们看一个基本算术计算的演示,了解模块是如何工作的。我们将进行一些计算,如加法、减法和乘法,并故意犯一些错误,使测试用例失败:

本节以下内容后续补齐。

9.8 图形用户界面测试工具

您可以轻松地将一个或多个类作为单元测试进行评估,但我们必须手动编写所有测试用例。图形用户界面测试尤其具有挑战性。我们如何才能在不使用C++或QML编码的情况下记录用户交互(如鼠标点击)?这个问题一直困扰着开发人员。市场上有许多图形用户界面测试工具可以帮助我们做到这一点。其中有些工具价格昂贵,有些则是开源的。我们将在本节中讨论几种这样的工具。

不过,你可能并不需要一个完整的图形用户界面测试框架。有些问题可以通过简单的技巧来解决。例如,在使用图形用户界面时,您可能还需要检查不同的属性,如视觉元素的对齐方式和边界。最简单的方法之一就是添加一个矩形来检查边界,如下面的代码所示:

在前面的示例中,您可以看到文本元素被置于矩形的中心位置,并带有蓝色边框。如果没有蓝色边框,你可能会想,为什么文本元素没有放在图形用户界面的中心位置。你还可以看到每个元素的边界和边距。当文本元素宽度小于字体宽度时,就会出现剪切现象。您还可以发现用户界面元素之间是否存在重叠区域。这样,就可以在不使用 SG_VISUALIZE 环境变量的情况下发现图形用户界面中特定元素的问题。

下面我们来讨论几个图形用户界面测试工具。

9.8.1 Linux 桌面测试项目(LDTP Linux Desktop Testing Project)

Linux桌面测试项目(LDTP)为测试和改进 Linux 桌面平台提供了高质量的测试自动化基础架构和尖端工具。LDTP是一个图形用户界面测试框架,可在所有平台上运行。它使用可访问性库对应用程序的用户界面进行探查。该框架还包括根据用户与图形用户界面的交互方式记录测试用例的工具。

要点击按钮,请使用以下语法:

click('<window name>','<button name>')

要获取给定对象的当前滑块值,请使用以下代码:

getslidervalue('<window name>','<slider name>')

要在 GUI 应用程序中使用 LDTP,您必须为所有QML对象添加一个可访问名称。您可以使用对象名称作为可访问名称,如下所示:

Button {
id: quitButton
objectName: "quitButton"
Accessible.name: objectName
}

在前面的代码中,我们为 QML 控件添加了一个可访问名称,这样 LDTP 工具就能找到这个按钮。LDTP 需要用户界面的窗口名称来定位子控件。假设窗口名称是 Example,那么要生成点击事件,请在 LDTP 脚本中使用以下命令:

>click('Example','quitButton'

前面的 LDTP 命令找到了 quitButton 并生成了一个按钮点击事件。您可以通过以下链接了解更多有关其功能和用途的信息:https://ldtp.freedesktop.org/user-doc/

9.8.2 GammaRay

KDAB 开发了一款名为 GammaRay 的软件自省工具,用于检查 Qt 应用程序。您可以使用 QObject 自省机制在运行时观察和操作应用程序。它既能在本地机器上运行,也能在远程嵌入式目标上运行。它扩展了指令级调试器的功能,同时遵循与底层框架相同的标准。这对于使用场景图、模型/视图、状态机等框架的复杂项目尤其有用。有多种工具可用于检查对象及其属性。然而,GammaRay 与 Qt 复杂框架的深度关联使其从众多工具中脱颖而出。

您可以从以下链接下载 GammaRay:https://github.com/KDAB/GammaRay/wiki/Getting-GammaRay

您可以从以下链接了解更多有关其功能和用途的信息:https://www.kdab.com/development-resources/qt-tools/gammaray/

9.8.3 Squish

Squish是一款跨平台图形用户界面自动化测试工具,适用于桌面、移动、嵌入式和网络应用程序。您可以对使用 Qt Widgets 或 Qt Quick 编写的跨平台应用程序进行图形用户界面自动化测试。全球数以千计的企业使用 Squish 通过功能回归测试和系统测试来测试图形用户界面。

您可以通过以下链接了解有关该工具的更多信息:https://www.froglogic.com/squish/

在本节中,我们讨论了各种图形用户界面测试工具。探索它们,并在你的项目中尝试使用。让我们总结一下本章的学习内容。

9.9 总结

在本章中,我们了解了什么是调试,以及如何使用不同的调试技术来识别 Qt 应用程序中的技术问题。除此之外,我们还了解了 Qt 在不同操作系统上支持的各种调试器。最后,我们学习了如何使用单元测试来简化某些调试措施。我们讨论了单元测试,并学习了如何使用 Qt 测试框架。你还看到了如何调试 Qt Quick 应用程序。我们还讨论了 Qt 支持的其他各种测试框架和工具。现在,您可以为自定义类编写单元测试。如果有人不小心修改了某些特定逻辑,单元测试就会失败并自动报警。

热门相关:罗曼史:妈妈的朋友2   见面4秒就插入女大学生   听话的秘书   我剩下的性爱时间   女婿面试