Qt程序单元测试学习记录 :下

GTest 使用 官方文档 https://github.com/google/googletest/blob/master/googletest/docs/primer.md
GMock 使用 官方文档 https://github.com/google/googletest/blob/master/googlemock/README.md
QtTest 使用 官方文档 https://doc.qt.io/qt-5/qttest-index.html
QSignalSpy 使用 官方文档 https://doc.qt.io/qt-5/qsignalspy.html


  

Qt程序单元测试学习记录 : 上

0. 前言
  0.1 单元的定义
  0.2 角色工作体系
  0.3 测试任务

Qt程序单元测试学习记录 :中

1. GTest/GMock 安装
  1.1 GTest 下载安装(Linux)
  1.2 GTest测试
  1.3 GMock 安装(Linux)
2. GTest基本使用
  2.1 GTest基本概念
  2.2 GTest 断言
  2.3 创建Test Suite
  2.4 创建 Test Fixtures
  2.5 调用GTest
  2.6 编写main()
  2.7 命令行输入测试
  2.8 如何测试私有函数
3. GMock基本使用
  3.1 什么是GMock
  3.2 使用GMock
  3.3 GMock案例

Qt程序单元测试学习记录 :下

4. GTest/GMock测试框架不足
5. Qt信号槽相关功能测试 QSignalSpy
  5.1 什么是QSignalSpy
6. Qt UI相关功能测试 Qt Test
  6.1 QtTest 介绍
  6.2 QtTest 鼠标键盘模拟
7. 测试方法
  7.1 组织产品代码和测试代码
  7.2 测试工程中如何引入被测代码
  7.3 解除类之间的依赖,何时mock

  


Qt程序单元测试学习记录 :下

    • 4. GTest/GMock测试框架不足
    • 5.Qt信号槽相关功能测试 QSignalSpy
      • 5.1 什么是QSignalSpy
          • 初始化
          • 常用函数
    • 6.Qt UI相关功能测试 Qt Test
      • 6.1 QtTest 介绍
      • 6.2 QtTest 鼠标键盘模拟
          • 键盘操作
          • 鼠标操作
    • 7.测试方法
      • 7.1 组织产品代码和测试代码
      • 7.2 测试工程中如何引入被测代码
      • 7.3 解除类之间的依赖,何时mock


4. GTest/GMock测试框架不足

  一般情况下GTest就够了,为什么要了解Qt Test
Qt Test优点:

  1. 用于对 (Qt)UI 组件进行单元测试的唯一框架,如果要模拟测试**(Qt)UI**,必须使用 Qt Test
  2. QSignalSpy:验证发射的信号(仅对Qt有用),可以与其他测试框架一起很好的使用
  3. 集成在Qt内部,无需第三方库

Qt Test缺点:

  1. 中文资料较少,官方文档介绍仅仅是基本使用,跟实际情况相差很多。使用Qt Test测试软件少,很难借鉴。
  2. 默认不支持 Test Fixtures (测试治具),必须自己手动创建
  3. QCOMPARE无法比较不同类型的值。
  4. 对Mock 支持很差
  5. 没有测试断言的测试
  6. 测试结构很奇怪
  7. Vs里比较麻烦

  也许不是最好的办法,但很多人方法和建议是使用GTest模板,配合QSignalSpy(Qt)UI模拟 来做测试。

可以看看
   https://stackoverflow.com/questions/1524390/what-unit-testing-framework-should-i-use-for-qt
   https://stackoverflow.com/questions/4879628/comparing-qtest-with-other-frameworks


5.Qt信号槽相关功能测试 QSignalSpy

5.1 什么是QSignalSpy

  QSignalSpy可以连接到任何对象的任何信号并记录其发射。 QSignalSpy本身是QVariant列表的列表。信号的每次发射都将在列表后面添加一个项目,其中包含信号的参数。

初始化
QSignalSpy spy(myPushButton, &QPushButton::clicked);
QSignalSpy spy(myPushButton, SIGNAL(clicked(bool)));
常用函数
函数 描述
QByteArray QSignalSpy::signal() const 返回当前监听类型
bool QSignalSpy::wait(int timeout = 5000) 启动事件循环,直到接收到给定信号为止。超时返回false
bool QSignalSpy::isValid() const 收到有效信号返回真

  比如:

/**
 * @brief TEST_F
 * DataBackupTest 备份数据测试
 */
TEST_F(ManageViewTest, DataBackupTest) {
    m_widget_->show();
    m_widget_->backup_manager_->Set_back_paths_({"./"});
    QSignalSpy spy(m_widget_, SIGNAL(SignalStartBackup()));
    ASSERT_EQ(0, spy.count());
    QTimer::singleShot(100, m_widget_, [ = ] {
        m_widget_->yes_button->click();
    });
    QTest::mouseClick(m_widget_->ui->backup_button, Qt::LeftButton);
    ASSERT_EQ(1, spy.count());
}
QSignalSpy spy(myCustomObject, SIGNAL(mySignal(int,QString,double)));

myCustomObject->doSomething(); // trigger emission of the signal

QList<QVariant> arguments = spy.takeFirst();
QVERIFY(arguments.at(0).type() == QVariant::Int);
QVERIFY(arguments.at(1).type() == QVariant::String);
QVERIFY(arguments.at(2).type() == QVariant::double);

6.Qt UI相关功能测试 Qt Test

6.1 QtTest 介绍

  QtTest类似GtTest,提供了一系列接口和宏用来做单元/集成测试。提供了几个初始化和析构函数,也有提供了自己的全局测试参数、各种断言以及跟cmaketest (CTest)对接的很多宏。
Qt程序单元测试学习记录 :下_第1张图片
  不过不用研究那么多测试框架,大家都大同小异。无非就是利用友元的特性创建测试函数。c++的测试框架GTest算是比较完善和资料很多的,QtTest虽然也不错但中文资料很少,官网说明也仅仅是使用介绍和基本例子。去github上下载小型开源软件的代码基本都是使用GTest,参考和借鉴别人思路更方便。
  当然,QtTest还是要了解的。界面相关测试肯定要模拟鼠标键盘,QtTest就提供了一系列模拟接口方便做界面测试。CTest和QtTest一起用,无疑大大提升开发效率。

6.2 QtTest 鼠标键盘模拟

  QtTest模拟键盘鼠标基本思路是:

  1. 确认需要模拟的Widget(比如按钮、输入栏)
  2. 确认位置(鼠标),默认情况在输入Widget的中间
  3. 确认键盘修饰(alt、shift等),默认无
  4. 确认模拟操作(移动、按下、松开、点击、双击、连续多次点击)
  5. 确认每次操作等待时间,默认0
  • 使用 QTest命名空间下的鼠标键盘模拟操作
键盘操作
函数 描述
QTest::keyPress 按下按键
QTest::keyRelease 释放按键
QTest::keyClick 点击键盘(按下并释放)。
QTest::keyClicks 连续点击键盘(按下并释放多次)。
QTest::keySequence 连续输入,相当于keyClicks。
QTest::Shortcut 快捷键(如果快捷键一个按键,就相当于keyClick。快捷键是组合键就相当于keyClicks)。
鼠标操作
函数 描述
QTest::MousePress 按下鼠标按钮。
QTest::MouseRelease 释放鼠标按钮。
QTest::MouseClick 单击鼠标按钮(按下并释放)。
QTest::MouseDClick 双击鼠标按钮(按下并释放两次)。
QTest::MouseMove 移动鼠标。

  比如:

/**
 * @brief TEST_F
 * 增加用户测试
 * 可以增加用户
 * 两次密码错误
 * 两次密码正确
 */
TEST_F(UserEditViewTest, AddUserTest) {
    QString use_name = "add";
    QSignalSpy spy(m_widget_, SIGNAL(SignalEditUserInfo(
                                         const QString, const QString,
                                         const EditType)));
    m_widget_->show();
    m_widget_->SetDialog(UserEditView::ADD, use_name);
    ASSERT_EQ("", m_widget_->ui->username_edit->text());
    // username_edit 可以输入
    QTest::keyPress(m_widget_->ui->username_edit, Qt::Key_A);
    QTest::keyPress(m_widget_->ui->username_edit, Qt::Key_D);
    QTest::keyPress(m_widget_->ui->username_edit, Qt::Key_D);
    ASSERT_EQ(use_name, m_widget_->ui->username_edit->text());
    // 输入错误密码
    QTest::keyPress(m_widget_->ui->password_edit_1, Qt::Key_1);
    QTest::keyPress(m_widget_->ui->password_edit_2, Qt::Key_2);
    QTest::mouseClick(m_widget_->ui->confirm_button, Qt::LeftButton);
    ASSERT_EQ(0, spy.count());
    // 输入正确密码
    QTest::keyPress(m_widget_->ui->password_edit_2, Qt::Key_Backspace);
    QTest::keyPress(m_widget_->ui->password_edit_2, Qt::Key_1);
    QTest::mouseClick(m_widget_->ui->confirm_button, Qt::LeftButton);
    ASSERT_EQ(1, spy.count());
    QList<QVariant> arguments = spy.takeFirst();
    ASSERT_EQ(arguments.at(0).toString(), use_name);
    ASSERT_EQ(arguments.at(1).toString(), "1");
    ASSERT_EQ(arguments.at(2).value<UserEditView::EditType>(), 1);
}
  • 使用 QTestEventList封装一系列鼠标键盘操作
      这个方法跟直接使用QTest命名空间下的鼠标键盘模拟操作区别就是可以把一系列模拟操作融合成一个操作合集,这个合集可以保存成数据,也可以在多个不同的widget操作。
      可以理解成填充QTest命名空间下的鼠标键盘模拟操作。AddXXX用来填充操作、addDelay用来填充延时、simulate用来执行模拟。

  比如:

/**
 * @brief TEST_F
 * MouseClick 鼠标点击验证
 */
TEST_F(FoldControlViewTest, MouseClick) {
    m_widget_->show();
    QSignalSpy spy(m_widget_, SIGNAL(SignalsHiddenButtonClicked()));
    QTestEventList events;
    events.addMouseClick(Qt::LeftButton);
    events.addDelay(200);
    events.simulate(m_widget_->ui->pushButton);
    events.simulate(m_widget_->ui->pushButton);
    events.simulate(m_widget_->ui->pushButton);
    ASSERT_EQ(spy.count(), 3);
}

7.测试方法

7.1 组织产品代码和测试代码

  我在github上拉一些完整的带测试工程的软件代码。

  • 软件比较大的话,一般在根目录会有一个文件夹放测试数据,单元/集成测试代码在源码下对应文件夹下本别有该文件夹内文件测试工程测试工程。
└── usr
├── 源码
│ │ └── 模块/功能/分类 A
│ │ │ │ └── A 相关源码
│ │ │ │ │ │ └── A 测试代码
│ │ └── 模块/功能/分类 B
│ │ │ │ └── B 相关源码
│ │ │ │ │ │ └── B 测试代码
│ │ └── 模块/功能/分类 C
│ │ │ │ └── C 相关源码
│ │ │ │ │ │ └── C 测试代码
├── 测试数据
│ │ │ │ └── ABC 测试数据
│ │ │ │ └── ABC 测试通用框架
  • 小型软件则是把所有测试工程和测试数据放在单独一个文件夹。
└── usr
├── 源码
│ │ └── 模块/功能/分类 A
│ │ │ │ └── A 相关源码
│ │ └── 模块/功能/分类 B
│ │ │ │ └── B 相关源码
│ │ └── 模块/功能/分类 C
│ │ │ │ └── C 相关源码
├── 测试数据
│ │ │ │ └── ABC 测试数据
│ │ │ │ └── ABC 测试通用框架
│ │ │ │ └── ABC 测试代码
│ │ │ │ │ │ └── A 测试代码
│ │ │ │ │ │ └── B 测试代码
│ │ │ │ │ │ └── C 测试代码

7.2 测试工程中如何引入被测代码

  一般有下边两种方法:

  • 将被测代码单独测试并且不包含其他任何产品代码。被测代码依赖的所有ThirdService、ThirdLibrary、软件内其他Class全部Mock掉。
  1. 基本需要把所有用的第三方库、自己内部所有代码全部mock一遍
  2. 适合依赖相对少
  3. 测试覆盖面广,代码可测性强
  4. 适合代码质量要求高,并且开发时间充裕的项目
  • 把产品代码编译成库,测试代码调用
  1. 大大减少Mock,缩短测试开发时间
  2. public函数测试时 :产品代码不会包含任何的测试代码
  3. private函数测试需要使用FRIEND_TEST,产品代码.h文件需要包含测试代码
  4. 测试代码可以调用整个程序资源,缩短测试开发时间
  5. 可以通过makefile来控制产品代码和测试代码(cmkae 的 enable_testing )

7.3 解除类之间的依赖,何时mock

  用过 gmock会发现当你使用ThirdService、ThirdLibrary时。由于gmock采用的是继承的方式,你需要自己重新实现一个ThirdService。
  当你真正想把被测代码隔离开了的时候,才进行Mock。

你可能感兴趣的:(Study-Qt)