name: qt-test-fixture-generator description: 生成包含模拟QObject信号与槽、数据驱动测试和GUI测试配置的Qt测试夹具 allowed-tools: Read, Write, Edit, Bash, Glob, Grep tags: [qt, 测试, 单元测试, 模拟, 夹具]
Qt测试夹具生成器
生成包含模拟QObject信号与槽、数据驱动测试和GUI测试配置的Qt测试夹具。此技能为Qt应用程序创建全面的测试基础设施。
功能
- 生成基于QTest的测试夹具
- 创建支持信号/槽的模拟QObjects
- 使用QFETCH设置数据驱动测试
- 配置使用QTestLib的GUI测试
- 为Qt类生成测试替身
- 设置基准测试
- 配置测试覆盖率报告
- 生成CMake测试集成
输入模式
{
"type": "object",
"properties": {
"projectPath": {
"type": "string",
"description": "Qt项目路径"
},
"classToTest": {
"type": "string",
"description": "要生成测试的类名"
},
"testType": {
"enum": ["unit", "integration", "gui", "benchmark"],
"default": "unit"
},
"mockDependencies": {
"type": "array",
"items": { "type": "string" },
"description": "需要模拟的类"
},
"dataProviders": {
"type": "array",
"items": {
"type": "object",
"properties": {
"testName": { "type": "string" },
"columns": { "type": "array" },
"rows": { "type": "array" }
}
}
},
"generateCoverage": {
"type": "boolean",
"default": true
}
},
"required": ["projectPath", "classToTest"]
}
输出模式
{
"type": "object",
"properties": {
"success": { "type": "boolean" },
"files": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": { "type": "string" },
"type": { "enum": ["test", "mock", "cmake"] }
}
}
},
"runCommand": { "type": "string" }
},
"required": ["success"]
}
生成的测试夹具
// tst_mywidget.cpp
#include <QtTest/QtTest>
#include <QSignalSpy>
#include "mywidget.h"
#include "mockservice.h"
class tst_MyWidget : public QObject
{
Q_OBJECT
private slots:
// 测试生命周期
void initTestCase(); // 所有测试前执行
void cleanupTestCase(); // 所有测试后执行
void init(); // 每个测试前执行
void cleanup(); // 每个测试后执行
// 单元测试
void testConstructor();
void testSetValue();
void testSetValue_data(); // 数据提供器
void testSignalEmission();
// GUI测试
void testButtonClick();
void testKeyboardInput();
// 基准测试
void benchmarkCalculation();
private:
MyWidget* m_widget;
MockService* m_mockService;
};
void tst_MyWidget::initTestCase()
{
// 一次性设置
qDebug() << "开始MyWidget测试";
}
void tst_MyWidget::cleanupTestCase()
{
// 一次性清理
}
void tst_MyWidget::init()
{
// 为每个测试创建新实例
m_mockService = new MockService(this);
m_widget = new MyWidget(m_mockService);
}
void tst_MyWidget::cleanup()
{
delete m_widget;
delete m_mockService;
m_widget = nullptr;
m_mockService = nullptr;
}
void tst_MyWidget::testConstructor()
{
QVERIFY(m_widget != nullptr);
QCOMPARE(m_widget->value(), 0);
QVERIFY(m_widget->isEnabled());
}
void tst_MyWidget::testSetValue_data()
{
// 数据驱动测试设置
QTest::addColumn<int>("input");
QTest::addColumn<int>("expected");
QTest::addColumn<bool>("shouldEmitSignal");
QTest::newRow("zero") << 0 << 0 << false;
QTest::newRow("positive") << 42 << 42 << true;
QTest::newRow("negative") << -10 << -10 << true;
QTest::newRow("max") << INT_MAX << INT_MAX << true;
}
void tst_MyWidget::testSetValue()
{
// 获取测试数据
QFETCH(int, input);
QFETCH(int, expected);
QFETCH(bool, shouldEmitSignal);
QSignalSpy spy(m_widget, &MyWidget::valueChanged);
m_widget->setValue(input);
QCOMPARE(m_widget->value(), expected);
QCOMPARE(spy.count(), shouldEmitSignal ? 1 : 0);
}
void tst_MyWidget::testSignalEmission()
{
QSignalSpy spy(m_widget, &MyWidget::valueChanged);
QVERIFY(spy.isValid());
m_widget->setValue(100);
QCOMPARE(spy.count(), 1);
QList<QVariant> arguments = spy.takeFirst();
QCOMPARE(arguments.at(0).toInt(), 100);
}
void tst_MyWidget::testButtonClick()
{
// GUI测试
QPushButton* button = m_widget->findChild<QPushButton*>("submitButton");
QVERIFY(button != nullptr);
QSignalSpy spy(m_widget, &MyWidget::submitted);
QTest::mouseClick(button, Qt::LeftButton);
QCOMPARE(spy.count(), 1);
}
void tst_MyWidget::testKeyboardInput()
{
QLineEdit* input = m_widget->findChild<QLineEdit*>("nameInput");
QVERIFY(input != nullptr);
input->setFocus();
QTest::keyClicks(input, "Hello World");
QCOMPARE(input->text(), QString("Hello World"));
// 测试键盘快捷键
QTest::keyClick(m_widget, Qt::Key_S, Qt::ControlModifier);
// 验证保存操作已触发
}
void tst_MyWidget::benchmarkCalculation()
{
QBENCHMARK {
m_widget->performCalculation();
}
}
QTEST_MAIN(tst_MyWidget)
#include "tst_mywidget.moc"
模拟对象
// mockservice.h
#include <QObject>
class MockService : public QObject
{
Q_OBJECT
public:
explicit MockService(QObject* parent = nullptr) : QObject(parent) {}
// 跟踪方法调用
int fetchDataCallCount() const { return m_fetchDataCalls; }
void resetCalls() { m_fetchDataCalls = 0; }
// 配置返回值
void setFetchDataResult(const QString& result) { m_fetchDataResult = result; }
public slots:
QString fetchData(int id) {
m_fetchDataCalls++;
m_lastFetchId = id;
emit dataRequested(id);
return m_fetchDataResult;
}
signals:
void dataRequested(int id);
public:
int lastFetchId() const { return m_lastFetchId; }
private:
int m_fetchDataCalls = 0;
int m_lastFetchId = -1;
QString m_fetchDataResult = "mock result";
};
CMake集成
# tests/CMakeLists.txt
find_package(Qt6 REQUIRED COMPONENTS Test)
enable_testing()
# 添加测试可执行文件
add_executable(tst_mywidget
tst_mywidget.cpp
mockservice.h
)
target_link_libraries(tst_mywidget PRIVATE
Qt6::Test
MyAppLib # 被测库
)
# 注册到CTest
add_test(NAME tst_mywidget COMMAND tst_mywidget)
# 覆盖率(使用gcov)
if(ENABLE_COVERAGE)
target_compile_options(tst_mywidget PRIVATE --coverage)
target_link_options(tst_mywidget PRIVATE --coverage)
endif()
运行测试
# 运行所有测试
ctest --test-dir build
# 运行特定测试
./build/tests/tst_mywidget
# 运行并显示详细输出
./build/tests/tst_mywidget -v1
# 运行特定测试函数
./build/tests/tst_mywidget testSetValue
# 运行数据驱动测试的特定数据行
./build/tests/tst_mywidget testSetValue:positive
# 为CI输出XML
./build/tests/tst_mywidget -o results.xml,xml
最佳实践
- 每个测试一个断言:保持测试专注
- 使用QSignalSpy:验证信号发射
- 使用数据驱动测试:避免代码重复
- 模拟依赖项:隔离被测单元
- 测试边界情况:边界值、空输入
- 清晰命名测试:描述预期行为
相关技能
qt-cmake-project-generator- 项目设置desktop-unit-testing流程 - 测试工作流cross-platform-test-matrix- CI测试
相关代理
qt-cpp-specialist- Qt专业知识desktop-test-architect- 测试策略