google 的C++自动化测试框架详解(Google Test)(2)

文章目录

  • [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);
}

集成建议

  1. 使用 Qt Test 的信号间谍:
cpp 复制代码
#include <QtTest/QSignalSpy>

TEST(WidgetTest, EmitsCorrectSignals) {
    QPushButton button;
    QSignalSpy spy(&button, &QPushButton::clicked);
    
    button.click();
    
    EXPECT_EQ(spy.count(), 1);
}
  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
)
  1. 最佳实践组合:
    • 用 Google Mock 模拟依赖对象

• 用 Qt Test 的 QSignalSpy 捕获信号

• 用 Google Test 参数化 测试多种输入

• 用 测试固件 设置 GUI 环境

• 用 死亡测试 验证异常处理

这些高级特性使得 Google Test 能够有效测试 Qt 应用程序,特别是信号槽机制和组件交互,同时保持测试的隔离性和可维护性。

相关推荐
小短腿的代码世界1 小时前
Qt_Qwt深度解析:从源码到工业级性能优化
开发语言·qt·性能优化
charlie1145141911 小时前
基于开源项目的现代C++实战——OnceCallback 实战(五):then 链式组合
开发语言·c++·开源
Shan12051 小时前
在C++中尝试封装为函数
开发语言·c++·算法
Shadow(⊙o⊙)1 小时前
Linux进程地址空间——钻入Linux内核架构性剖析 硬核手搓!
java·linux·运维·服务器·开发语言·c++
GoKu~1 小时前
QT Qss
qt
郝学胜-神的一滴1 小时前
干货版《算法导论》04:渐近复杂度与序列接口实战
java·开发语言·数据结构·c++·python·算法
Peter·Pan爱编程1 小时前
构造与析构:对象生命周期的“自动挡“
c++
基德爆肝c语言2 小时前
Qt系统相关
开发语言·qt
redaijufeng2 小时前
C/C++程序从编译到链接的过程
c语言·开发语言·c++