一、编写单元测试
本节是关于如何编写一个简单的单元测试类,以及如何执行测试。
假设想测试 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() **宏调用。
版权归原作者 友善啊,朋友 所有, 如有侵权,请联系我们删除。