Qt Test是用于对基于Qt的应用程序和库进行单元测试的框架。Qt Test提供了单元测试框架中常见的所有功能以及用于测试图形用户界面的扩展。
创建测试
-
测试类继承QObject
-
向测试类添加private slot,一个slot代表一个测试用例。
-
QTest :: qExec()可用于执行测试对象中的所有测试功能。
官方用例
class MyFirstTest: public QObject
{
Q_OBJECT
private:
bool myCondition() {
return true;
}
private slots:
void initTestCase() {
qDebug("Called before everything else.");
}
void myFirstTest() {
QVERIFY(true); // check that a condition is satisfied
QCOMPARE(1, 1); // compare two values
}
void mySecondTest() {
QVERIFY(myCondition());
QVERIFY(1 != 2);
}
void cleanupTestCase() {
qDebug("Called after myFirstTest and mySecondTest.");
}
};
如果测试类具有静态公共void initMain()
方法,则在实例化QApplication对象之前,QTEST_MAIN宏将调用该方法。例如,这允许设置应用程序属性,例如Qt :: AA_DisableHighDpiScaling。这是在5.14中添加的。
几个说明
可以看qt-everywhere-src-5.12.8\qtbase\src\testlib\qtest.h
#define QTEST_MAIN(TestObject) \
QT_BEGIN_NAMESPACE \
QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS \
QT_END_NAMESPACE \
int main(int argc, char *argv[]) \
{ \
QApplication app(argc, argv); \
app.setAttribute(Qt::AA_Use96Dpi, true); \
QTEST_DISABLE_KEYPAD_NAVIGATION \
QTEST_ADD_GPU_BLACKLIST_SUPPORT \
TestObject tc; \
QTEST_SET_MAIN_SOURCE_PATH \
return QTest::qExec(&tc, argc, argv); \
}
既可以手动写main也可以直接在测试类后加QTEST_MAIN(testclass)
可以定义如下的private slot可以在当前整个测试前后或每个用例测试前后进行一些资源初始化和释放。
initTestCase_data()
将被调用以创建全局测试数据表。initTestCase()
将在第一个测试功能执行之前被调用。cleanupTestCase()
在最后一个测试函数执行后将被调用。init()
将在每个测试功能执行之前被调用。cleanup()
将在每个测试函数之后调用。
使用RAII(资源获取是初始化),并在析构函数中调用清除操作,以确保它们在测试函数返回且对象移出作用域时发生。即也可以自己写构造析构来进行部分资源的申请和释放。
QT_KEYPAD_NAVIGATION
#ifdef QT_KEYPAD_NAVIGATION
# define QTEST_DISABLE_KEYPAD_NAVIGATION QApplication::setNavigationMode(Qt::NavigationModeNone);
#else
# define QTEST_DISABLE_KEYPAD_NAVIGATION
#endif
枚举Qt :: NavigationMode,此枚举类型描述移动焦点的模式。
名称 | 值 | 描述 |
---|---|---|
Qt::NavigationModeNone |
0 |
仅使用触摸屏。 |
Qt::NavigationModeKeypadTabOrder |
1 |
Qt :: Key_Up和Qt :: Key_Down用于更改焦点。 |
Qt::NavigationModeKeypadDirectional |
2 |
Qt :: Key_Up,Qt :: Key_Down,Qt :: Key_Left和Qt :: Key_Right用于更改焦点。 |
Qt::NavigationModeCursorAuto |
3 |
鼠标光标用于更改焦点,仅在非触摸屏设备上显示。除非设备具有模拟鼠标类型的输入设备(例如触摸板),否则小键盘用于实现虚拟光标。对于需要同时在触摸和非触摸设备上进行指针控制的Web浏览器之类的应用程序,建议使用此设置。 |
Qt::NavigationModeCursorForceVisible |
4 |
鼠标光标用于更改焦点,无论设备类型如何,鼠标光标都会显示。键盘用于实现虚拟光标,除非设备具有模拟鼠标类型的输入设备(例如触摸板) |
即由QT_KEYPAD_NAVIGATION宏定义是否禁用键盘鼠标(仅使用触摸屏)
app.setAttribute(Qt::AA_Use96Dpi, true);
Qt::AA_Use96Dpi |
8 |
假定屏幕的分辨率为96 DPI,而不是使用操作系统提供的分辨率。这将导致字体渲染在设备上的每像素像素一致,而不是将1点定义为1/72英寸。 |
---|---|---|
QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS
#ifndef QT_NO_OPENGL
# define QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS \
extern Q_TESTLIB_EXPORT std::set<QByteArray> *(*qgpu_features_ptr)(const QString &); \
extern Q_GUI_EXPORT std::set<QByteArray> *qgpu_features(const QString &);
# define QTEST_ADD_GPU_BLACKLIST_SUPPORT \
qgpu_features_ptr = qgpu_features;
#else
# define QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS
# define QTEST_ADD_GPU_BLACKLIST_SUPPORT
#endif
QTEST_SET_MAIN_SOURCE_PATH
#ifdef QT_TESTCASE_BUILDDIR
# define QTEST_SET_MAIN_SOURCE_PATH QTest::setMainSourcePath(__FILE__, QT_TESTCASE_BUILDDIR);
#else
# define QTEST_SET_MAIN_SOURCE_PATH QTest::setMainSourcePath(__FILE__);
#endif
->void QTest::setMainSourcePath(const char *file, const char *builddir)
->>QTest::mainSourcePath = fi.absolutePath();
->>>static QString mainSourcePath;
QT源码qtest库Demo
下面几个是源qt-everywhere-src-5.12.8\qtbase\examples\qtestlib
中的几个demo,工程是QT工程。
tutorial1.pro
QT += widgets testlib
SOURCES = testqstring.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/qtestlib/tutorial1
INSTALLS += target
testqstring.cpp
//! [0]
#include <QtTest/QtTest>
class TestQString: public QObject
{
Q_OBJECT
private slots:
void toUpper();
};
//! [0]
//! [1]
void TestQString::toUpper()
{
QString str = "Hello";
QCOMPARE(str.toUpper(), QString("HELLO"));
}
//! [1]
//! [2]
QTEST_MAIN(TestQString)
#include "testqstring.moc"
//! [2]
tutorial2.pro
QT += widgets testlib
SOURCES = testqstring.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/qtestlib/tutorial2
INSTALLS += target
testqstring.cpp
#include <QtTest/QtTest>
//! [0]
class TestQString: public QObject
{
Q_OBJECT
private slots:
void toUpper_data();
void toUpper();
};
//! [0]
//! [1]
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";
}
//! [1]
//! [2]
void TestQString::toUpper()
{
QFETCH(QString, string);
QFETCH(QString, result);
QCOMPARE(string.toUpper(), result);
}
//! [2]
//! [3]
QTEST_MAIN(TestQString)
#include "testqstring.moc"
//! [3]
tutorial3.pro
QT += widgets testlib
SOURCES = testgui.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/qtestlib/tutorial3
INSTALLS += target
testgui.cpp
//! [0]
#include <QtWidgets>
#include <QtTest/QtTest>
class TestGui: public QObject
{
Q_OBJECT
private slots:
void testGui();
};
//! [0]
//! [1]
void TestGui::testGui()
{
QLineEdit lineEdit;
QTest::keyClicks(&lineEdit, "hello world");
QCOMPARE(lineEdit.text(), QString("hello world"));
}
//! [1]
//! [2]
QTEST_MAIN(TestGui)
#include "testgui.moc"
//! [2]
tutorial4.pro
QT += widgets testlib
SOURCES = testgui.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/qtestlib/tutorial4
INSTALLS += targe
testgui.cpp
#include <QtWidgets>
#include <QtTest/QtTest>
//! [0]
class TestGui: public QObject
{
Q_OBJECT
private slots:
void testGui_data();
void testGui();
};
//! [0]
//! [1]
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 << "";
}
//! [1]
//! [2]
void TestGui::testGui()
{
QFETCH(QTestEventList, events);
QFETCH(QString, expected);
QLineEdit lineEdit;
events.simulate(&lineEdit);
QCOMPARE(lineEdit.text(), expected);
}
//! [2]
//! [3]
QTEST_MAIN(TestGui)
#include "testgui.moc"
//! [3]
tutorial5.pro
QT += widgets testlib
SOURCES = benchmarking.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/qtestlib/tutorial5
INSTALLS += target
benchmarking.cpp
#include <QtWidgets>
#include <qtest.h>
class TestBenchmark : public QObject
{
Q_OBJECT
private slots:
void simple();
void multiple_data();
void multiple();
void series_data();
void series();
};
//! [0]
void TestBenchmark::simple()
{
QString str1 = QLatin1String("This is a test string");
QString str2 = QLatin1String("This is a test string");
QCOMPARE(str1.localeAwareCompare(str2), 0);
QBENCHMARK {
str1.localeAwareCompare(str2);
}
}
//! [0]
//! [1]
void TestBenchmark::multiple_data()
{
QTest::addColumn<bool>("useLocaleCompare");
QTest::newRow("locale aware compare") << true;
QTest::newRow("standard compare") << false;
}
//! [1]
//! [2]
void TestBenchmark::multiple()
{
QFETCH(bool, useLocaleCompare);
QString str1 = QLatin1String("This is a test string");
QString str2 = QLatin1String("This is a test string");
int result;
if (useLocaleCompare) {
QBENCHMARK {
result = str1.localeAwareCompare(str2);
}
} else {
QBENCHMARK {
result = (str1 == str2);
}
}
Q_UNUSED(result);
}
//! [2]
//! [3]
void TestBenchmark::series_data()
{
QTest::addColumn<bool>("useLocaleCompare");
QTest::addColumn<int>("stringSize");
for (int i = 1; i < 10000; i += 2000) {
QByteArray size = QByteArray::number(i);
QTest::newRow(("locale aware compare--" + size).constData()) << true << i;
QTest::newRow(("standard compare--" + size).constData()) << false << i;
}
}
//! [4]
//! [5]
void TestBenchmark::series()
{
QFETCH(bool, useLocaleCompare);
QFETCH(int, stringSize);
QString str1 = QString().fill('A', stringSize);
QString str2 = QString().fill('A', stringSize);
int result;
if (useLocaleCompare) {
QBENCHMARK {
result = str1.localeAwareCompare(str2);
}
} else {
QBENCHMARK {
result = (str1 == str2);
}
}
Q_UNUSED(result);
}
//! [5]
QTEST_MAIN(TestBenchmark)
#include "benchmarking.moc"
命令行测试
/myTestDirectory$ testQString toUpper
/myTestDirectory$ testQString toUpper toInt:zero
/myTestDirectory$ testMyWidget -vs -eventdelay 500
可以指定单个用例进行测试,可以带参数(没用过),还可以指定鼠标键盘事件的延时触发(没用过)
可以参考命令行测试选项
测试API
CMake编译
如果需要转成CMake,参考[Cmake编译QT项目](https://blog.csdn.net/bbdxf/article/details/82988468)。
需要注意用了set(CMAKE_AUTOMOC ON),就不能用qt5_wrap_cpp(MOC ${HEAD_FILES})了否则可能报错。
# AUTOMOC是一个布尔值,指定CMake是否将moc 自动处理Qt预处理程序,
# 即无需使用 QT4_WRAP_CPP()或QT5_WRAP_CPP()宏。当前支持Qt4和Qt5。
set(CMAKE_AUTOMOC ON)
# AUTOUIC是一个布尔值,指定CMake是否自动处理Qt uic代码生成器,
# 即不必使用QT4_WRAP_UI()或QT5_WRAP_UI()宏。当前支持Qt4和Qt5。
set(CMAKE_AUTOUIC ON)
# AUTORCC是一个布尔值,指定CMake是否将rcc自动处理Qt代码生成器,
# 即无需使用QT4_ADD_RESOURCES()或QT5_ADD_RESOURCES() 宏。当前支持Qt4和Qt5。
set(CMAKE_AUTORCC ON)
# process ui and moc action
#qt5_wrap_cpp(MOC ${HEAD_FILES})
qt5_wrap_ui(WRAP_FILES ${UI_FILES})
# qt5_add_resources(RCC res.qrc) # ${RESOURCE_DIR}
开启全局后,可以直接加入到编译的测试目标中即可
add_executable(${PROJECT_NAME} ${SRC_FILES} ${UI_FILES} ${QRC_FILE})
注意对应的package和lib名
find_package(Qt5Test REQUIRED)
target_link_libraries(mytest PRIVATE Qt5::Test)