Qt 单元测试的用法

一、编写单元测试

本节是关于如何编写一个简单的单元测试类,以及如何执行测试。

假设想测试 QString 类的行为。首先,需要一个包含测试函数的类,这个类必须继承自 QObject

#include <QTest>

class TestQString: public QObject
{
    Q_OBJECT
private slots:
    void toUpper();
};

需要包含 QTest 头文件并将测试函数声明为私有槽函数,以便测试框架找到并执行它。

测试函数实现:

void TestQString::toUpper()
{
    QString str = "Hello";
    QVERIFY(str.toUpper() == "HELLO");
}

QVERIFY() 宏计算作为其参数传递的表达式。表达式的结果:

  • 为 true 则继续执行测试函数。
  • 为 false 则向测试日志添加描述失败的消息,并且测试函数会停止执行

测试失败时:

测试成功时:

如果想要更详细的测试日志输出,可以改用 QCOMPARE() 宏:

void TestQString::toUpper()
{
    QString str = "Hello";
    QCOMPARE(str.toUpper(), QString("HELLO"));
}

使用 QCOMPARE() 宏测试成功时输出和上面一样。测试失败时会显示更多信息:

最后,为了使测试用例成为一个独立的可执行文件,需要以下两行:

QTEST_MAIN(TestQString)    //放在.cpp文件
#include "testqstring.moc"

QTEST_MAIN() 宏扩展为运行所有测试函数的简单 main() 方法。如果测试类的声明和实现都在一个 .cpp 文件中,还需要包含生成的 moc 文件以使 Qt 的反射机制工作。

二、使用不同的测试数据多次执行测试

如果想要添加更多测试数据,如下:

QCOMPARE(QString("hello").toUpper(), QString("HELLO"));
QCOMPARE(QString("Hello").toUpper(), QString("HELLO"));
QCOMPARE(QString("HellO").toUpper(), QString("HELLO"));
QCOMPARE(QString("HELLO").toUpper(), QString("HELLO"));

为了防止函数最终被重复代码弄乱,Qt Test 支持将测试数据添加到测试函数中。只需要在测试类中添加另一个私有槽:

class TestQString: public QObject
{
    Q_OBJECT

private slots:
    void toUpper_data();
    void toUpper();
};

测试函数关联数据函数具有相同的名称,并附加了 _data

void TestQString::toUpper_data()
{
    QTest::addColumn<QString>("string");
    QTest::addColumn<QString>("result");

    QTest::newRow("all lower") << "hello" << "HELLO";//数据集 all lower
    QTest::newRow("mixed")     << "Hello" << "HELLO";//数据集 mixed
    QTest::newRow("all upper") << "HELLO" << "HELLO";//数据集 all upper
}

这里使用 QTest::addColumn() 函数定义测试表的两个元素:一个测试字符串,以及将 QString::toUpper() 函数应用于该字符串的预期结果。

然后使用 QTest::newRow() 函数将一些数据添加到表中。每组数据将成为测试表中的单独行。

QTest::newRow() 接受一个参数:将与数据集关联并在测试日志中用于标识数据集的名称。然后将数据集流式传输到新的表行中。首先是一个任意字符串,然后将 QString::toUpper() 函数应用于该字符串的预期结果。

可以将测试数据视为一个二维表。上面的测试数据可视为下表。此外,数据集的名称和索引与每一行相关联:

当数据流入行时,每个数据都被断言以匹配它提供的值的列的类型。如果任何断言失败,则中止测试。

测试函数重写为:

void TestQString::toUpper()
{
    QFETCH(QString, string);
    QFETCH(QString, result);

    QCOMPARE(string.toUpper(), result);
}

TestQString::toUpper() 函数将执行 3 次,对在关联的 TestQString::toUpper_data() 函数中创建的测试表中的每个条目执行一次。

首先,使用 QFETCH() 宏获取数据集的两个元素。QFETCH() 接受两个参数:元素的数据类型和元素名称。然后使用 QCOMPARE() 宏执行测试。

这种方法可以很容易地将新数据添加到测试中,而无需修改测试代码。

代码汇总:

#include <QTest>

class TestQString: public QObject
{
    Q_OBJECT

private slots:
    void toUpper_data();
    void toUpper();
};

void TestQString::toUpper_data()
{
    QTest::addColumn<QString>("string");
    QTest::addColumn<QString>("result");

    QTest::newRow("all lower") << "hello" << "HELLO";
    QTest::newRow("mixed")     << "Hello" << "HELLO";
    QTest::newRow("all upper") << "HELLO" << "HELLO";
}

void TestQString::toUpper()
{
    QFETCH(QString, string);
    QFETCH(QString, result);

    QCOMPARE(string.toUpper(), result);
}

QTEST_MAIN(TestQString)
#include "testqstring.moc"

三、模拟 GUI 事件

Qt Test 具有测试图形用户界面的机制。其原理不是模拟本地窗口系统事件,而是发送内部 Qt 事件。

假设想要测试 QLineEdit 类的行为。需要一个包含测试函数的类:

#include <QtWidgets>
#include <QTest>

class TestGui : public QObject
{
    Q_OBJECT
    
private slots:
    void testGui();
};

void TestGui::testGui()
{
    QLineEdit lineEdit;
    QTest::keyClicks(&lineEdit, "hello world");
    QCOMPARE(lineEdit.text(), QString("hello world"));
}

在执行测试函数时首先创建一个 QLineEdit。然后使用 QTest::keyClicks() 函数生成键盘按下事件在QLineEdit 中模拟编写“hello world”。

最后,使用 QCOMPARE() 宏来检查行编辑的文本是否符合预期。

四、存储和重播 GUI 事件

存储一系列事件并重放它们的方法与第 2 节中的方法非常相似。需要做的就是在测试类中添加一个数据函数:

class TestGui: public QObject
{
    Q_OBJECT

private slots:
    void testGui_data();
    void testGui();
};

和之前一样,测试函数的关联数据函数具有相同的名称,并附加了 _data

void TestGui::testGui_data()
{
    QTest::addColumn<QTestEventList>("events");
    QTest::addColumn<QString>("expected");

    QTestEventList list1;
    list1.addKeyClick('a');
    QTest::newRow("char") << list1 << "a";

    QTestEventList list2;
    list2.addKeyClick('a');
    list2.addKeyClick(Qt::Key_Backspace);
    QTest::newRow("there and back again") << list2 << "";
}

首先,使用 QTest::addColumn() 函数定义二维表的元素:GUI 事件列表,以及在 QWidget 上应用事件列表的预期结果。

QTestEventList 可以填充 GUI 事件,这些事件可以存储为测试数据以供以后使用,或者在任何 QWidget 上重放。

这里创建了两个 QTestEventList 元素:

第一个列表由单击“a”键组成。使用 addKeyClick() 函数将事件添加到列表中。然后使用 QTest::newRow() 将此数据集命名为“char”,并将事件列表和预期结果流式传输到表中。

第二个列表由两个键单击组成:一个“a”和一个“退格”。

重写测试函数:

void TestGui::testGui()
{
    QFETCH(QTestEventList, events);
    QFETCH(QString, expected);

    QLineEdit lineEdit;

    events.simulate(&lineEdit);

    QCOMPARE(lineEdit.text(), expected);
}

TestGui::testGui() 函数将被执行两次,对于在关联的 TestGui::testGui_data() 函数中创建的测试数据中的每个条目执行一次。

首先,使用 QFETCH() 宏获取数据集的两个元素。然后创建一个 QLineEdit,并使用 QTestEventList::simulate() 函数在该小部件上应用事件列表。

最后,使用 QCOMPARE() 宏来检查行编辑的文本是否符合预期。

五、使用 QSKIP 跳过测试

如果从测试函数中调用 QSKIP() 宏,它会停止测试的执行,而不会将失败信息添加到测试日志中。QSKIP() 描述参数中的文本附加到测试日志中,并解释了未执行测试的原因。

当测试尚未完成或在某个平台上不受支持的功能时,QSKIP() 可用于跳过测试。

当存在已知故障时,可使用 QEXPECT_FAIL(),因为它支持在可能的情况下运行其余的测试。

如果从 _data 函数调用,QSKIP() 宏将停止执行 _data 函数。这会阻止执行相关的测试功能。

如果从 initTestCase() 或 initTestCase_data() 调用,QSKIP() 宏将跳过所有测试和 _data 函数。

六、单元测试相关的函数

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

此外,可以定义以下不被视为测试函数的私有槽。如果存在,它们将由测试框架执行,可用于初始化和清理整个测试或当前测试功能。

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

如果 initTestCase() 失败,则不会执行任何测试函数。

如果 init() 失败,则不会执行下面的测试函数,将继续下一个测试函数。

最后,如果测试类有一个静态的 public void initMain() 方法,它会在 QApplication 对象被实例化之前由 QTEST_MAIN() 宏调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值