文章目录
- [1. Google Mock(模拟框架)](#1. Google Mock(模拟框架))
- [2. 参数化测试](#2. 参数化测试)
- [3. 类型参数化测试](#3. 类型参数化测试)
- [4. 值参数化测试 + 模拟信号](#4. 值参数化测试 + 模拟信号)
- [5. 模拟定时器和异步操作](#5. 模拟定时器和异步操作)
- [6. 死亡测试 + GUI 异常处理](#6. 死亡测试 + GUI 异常处理)
- [7. 测试固件 + 模拟 GUI 事件](#7. 测试固件 + 模拟 GUI 事件)
Google Test 结合 Google Mock 和 Qt 测试工具,可以很好地测试 Qt 的信号槽和 GUI 组件。以下是适合 Qt 开发的高级特性:
1. Google Mock(模拟框架)
这是测试 Qt 信号槽的核心工具,可以模拟 Qt 对象间的交互。
cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <QObject>
#include <QSignalSpy>
#include <QTimer>
// 模拟接口
class IDataProvider : public QObject {
Q_OBJECT
public:
virtual void fetchData() = 0;
signals:
void dataReady(const QString& data);
void errorOccurred(const QString& error);
};
// Mock 类
class MockDataProvider : public IDataProvider {
public:
MOCK_METHOD(void, fetchData, (), (override));
// 模拟信号发射
void emitDataReady(const QString& data) {
emit dataReady(data);
}
void emitError(const QString& error) {
emit errorOccurred(error);
}
};
// 被测类
class DataProcessor : public QObject {
Q_OBJECT
public:
DataProcessor(IDataProvider* provider) : m_provider(provider) {
connect(m_provider, &IDataProvider::dataReady,
this, &DataProcessor::onDataReady);
connect(m_provider, &IDataProvider::errorOccurred,
this, &DataProcessor::onError);
}
void process() { m_provider->fetchData(); }
signals:
void processingComplete(const QString& result);
void processingFailed(const QString& reason);
private slots:
void onDataReady(const QString& data) {
// 处理数据
emit processingComplete("Processed: " + data);
}
void onError(const QString& error) {
emit processingFailed("Error: " + error);
}
private:
IDataProvider* m_provider;
};
// 测试
TEST(DataProcessorTest, HandlesDataReady) {
MockDataProvider mock;
DataProcessor processor(&mock);
QSignalSpy completeSpy(&processor, &DataProcessor::processingComplete);
EXPECT_CALL(mock, fetchData()).Times(1);
// 触发处理
processor.process();
// 模拟数据到达
mock.emitDataReady("test data");
EXPECT_EQ(completeSpy.count(), 1);
EXPECT_EQ(completeSpy.at(0).at(0).toString(),
"Processed: test data");
}
2. 参数化测试
非常适合测试不同输入下的 GUI 组件行为。
cpp
#include <gtest/gtest.h>
#include <QLineEdit>
#include <QValidator>
class LineEditValidationTest :
public ::testing::TestWithParam<std::tuple<QString, bool>> {
protected:
void SetUp() override {
lineEdit = new QLineEdit;
// 只允许数字
lineEdit->setValidator(new QIntValidator(0, 100, lineEdit));
}
void TearDown() override {
delete lineEdit;
}
QLineEdit* lineEdit;
};
TEST_P(LineEditValidationTest, ValidatesInputCorrectly) {
auto [input, expectedValid] = GetParam();
lineEdit->setText(input);
int pos = 0;
QString text = lineEdit->text();
const QValidator* validator = lineEdit->validator();
QValidator::State state = validator->validate(text, pos);
bool isValid = (state == QValidator::Acceptable);
EXPECT_EQ(isValid, expectedValid);
}
INSTANTIATE_TEST_SUITE_P(
ValidationTests,
LineEditValidationTest,
::testing::Values(
std::make_tuple("0", true),
std::make_tuple("50", true),
std::make_tuple("100", true),
std::make_tuple("-1", false),
std::make_tuple("101", false),
std::make_tuple("abc", false),
std::make_tuple("12.3", false)
)
);
3. 类型参数化测试
测试不同类型的 GUI 组件。
cpp
template <typename T>
class WidgetFocusTest : public ::testing::Test {
protected:
void SetUp() override {
widget = new T;
}
void TearDown() override {
delete widget;
}
T* widget;
};
using WidgetTypes = ::testing::Types<QLineEdit, QComboBox, QSpinBox>;
TYPED_TEST_SUITE(WidgetFocusTest, WidgetTypes);
TYPED_TEST(WidgetFocusTest, CanReceiveAndLoseFocus) {
ASSERT_FALSE(this->widget->hasFocus());
this->widget->setFocus();
EXPECT_TRUE(this->widget->hasFocus());
// 模拟失去焦点
QFocusEvent focusOut(QEvent::FocusOut);
QCoreApplication::sendEvent(this->widget, &focusOut);
EXPECT_FALSE(this->widget->hasFocus());
}
4. 值参数化测试 + 模拟信号
结合信号测试多个场景。
cpp
class ButtonClickTest :
public ::testing::TestWithParam<std::tuple<int, QString>> {
protected:
void SetUp() override {
button = new QPushButton;
label = new QLabel;
connect(button, &QPushButton::clicked, [this]() {
auto [clickCount, expectedText] = GetParam();
static int count = 0;
count++;
if (count == clickCount) {
label->setText(expectedText);
}
});
}
QPushButton* button;
QLabel* label;
};
TEST_P(ButtonClickTest, UpdatesLabelAfterClicks) {
auto [clickCount, expectedText] = GetParam();
for (int i = 0; i < clickCount; ++i) {
button->click();
}
EXPECT_EQ(label->text(), expectedText);
}
INSTANTIATE_TEST_SUITE_P(
ButtonTests,
ButtonClickTest,
::testing::Values(
std::make_tuple(1, "Clicked once"),
std::make_tuple(3, "Clicked 3 times"),
std::make_tuple(5, "Clicked 5 times")
)
);
5. 模拟定时器和异步操作
cpp
class AsyncOperationTest : public ::testing::Test {
protected:
void SetUp() override {
// 创建事件循环用于等待异步操作
loop = new QEventLoop;
timeoutTimer = new QTimer(this);
timeoutTimer->setSingleShot(true);
connect(timeoutTimer, &QTimer::timeout,
loop, &QEventLoop::quit);
}
// 等待信号,带超时
bool waitForSignal(QObject* sender, const char* signal,
int timeout = 1000) {
QTimer::singleShot(timeout, loop, &QEventLoop::quit);
bool signalReceived = false;
auto connection = QObject::connect(sender, signal,
[&]() {
signalReceived = true;
loop->quit();
});
loop->exec();
QObject::disconnect(connection);
return signalReceived;
}
QEventLoop* loop;
QTimer* timeoutTimer;
};
TEST_F(AsyncOperationTest, CompletesWithinTime) {
QTimer* operationTimer = new QTimer(this);
operationTimer->setSingleShot(true);
// 模拟耗时操作
connect(operationTimer, &QTimer::timeout,
[]() { /* 完成操作 */ });
operationTimer->start(100);
// 等待完成信号
bool completed = waitForSignal(operationTimer,
SIGNAL(timeout()));
EXPECT_TRUE(completed);
EXPECT_FALSE(operationTimer->isActive());
}
6. 死亡测试 + GUI 异常处理
cpp
TEST(WidgetDeathTest, HandlesNullPointerGracefully) {
QWidget* widget = nullptr;
// 测试访问空指针是否会崩溃
ASSERT_DEATH({
if (widget) {
widget->show(); // 这行不会执行
} else {
// 模拟程序终止(实际中应该抛出异常)
std::terminate();
}
}, "");
}
7. 测试固件 + 模拟 GUI 事件
cpp
class WidgetEventTest : public ::testing::Test {
protected:
void SetUp() override {
widget = new QWidget;
button = new QPushButton("Test", widget);
connect(button, &QPushButton::clicked,
this, &WidgetEventTest::onButtonClicked);
}
void TearDown() override {
delete widget;
}
void simulateClick(const QPoint& pos) {
QMouseEvent pressEvent(QEvent::MouseButtonPress, pos,
Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
QMouseEvent releaseEvent(QEvent::MouseButtonRelease, pos,
Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::sendEvent(button, &pressEvent);
QCoreApplication::sendEvent(button, &releaseEvent);
}
public slots:
void onButtonClicked() { clickCount++; }
QWidget* widget;
QPushButton* button;
int clickCount = 0;
};
TEST_F(WidgetEventTest, ReceivesMouseEvents) {
// 确保按钮可见
widget->show();
// 模拟点击按钮中心
QPoint center = button->rect().center();
simulateClick(center);
// 处理事件队列
QCoreApplication::processEvents();
EXPECT_EQ(clickCount, 1);
}
集成建议
- 使用 Qt Test 的信号间谍:
cpp
#include <QtTest/QSignalSpy>
TEST(WidgetTest, EmitsCorrectSignals) {
QPushButton button;
QSignalSpy spy(&button, &QPushButton::clicked);
button.click();
EXPECT_EQ(spy.count(), 1);
}
- CMake 集成配置:
cpp
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Test)
find_package(GTest REQUIRED)
find_package(GMock REQUIRED)
add_executable(MyTests
test_main.cpp
widget_tests.cpp
signal_tests.cpp
)
target_link_libraries(MyTests
Qt5::Core
Qt5::Widgets
Qt5::Test
GTest::gtest
GMock::gmock
)
- 最佳实践组合:
• 用 Google Mock 模拟依赖对象
• 用 Qt Test 的 QSignalSpy 捕获信号
• 用 Google Test 参数化 测试多种输入
• 用 测试固件 设置 GUI 环境
• 用 死亡测试 验证异常处理
这些高级特性使得 Google Test 能够有效测试 Qt 应用程序,特别是信号槽机制和组件交互,同时保持测试的隔离性和可维护性。