输入关键词开始搜索

Qt 单元测试 — QTest 框架

最小测试用例

#include <QTest>

class TestMath : public QObject {
    Q_OBJECT
private slots:
    void testAddition() {
        QCOMPARE(1 + 1, 2);
    }
    void testDivision() {
        QVERIFY(10 / 2 == 5);
        QVERIFY2(10 / 3 == 3, "整数除法应截断");  // 带消息
    }
};

QTEST_MAIN(TestMath)  // 自动生成 main

常用断言

QVERIFY(condition);                    // 真
QVERIFY2(condition, "message");       // 真 + 消息

QCOMPARE(actual, expected);           // 相等(自动类型安全)
QCOMPARE(str, "hello");               // QString 直接比较
QCOMPARE(list.size(), 3);

QTRY_COMPARE(obj.property(), value);  // 重试直到相等(异步测试)
QTRY_VERIFY(condition);               // 重试直到 true

QVERIFY_EXCEPTION_THROWN(expr, Exc);  // 确认抛异常
QBENCHMARK { doWork(); }              // 性能测试

初始化和清理

class TestDatabase : public QObject {
    Q_OBJECT
private:
    QSqlDatabase *db;
private slots:
    // 每个 test 用例前
    void init() { db = new QSqlDatabase(...); }
    // 每个 test 用例后
    void cleanup() { delete db; db = nullptr; }

    // 所有用例前(private slot!)
    void initTestCase() { /* 建表、加载测试数据 */ }
    // 所有用例后
    void cleanupTestCase() { /* 清空数据库 */ }

    void testInsert() { /* ... */ }
    void testQuery() { /* ... */ }
};

数据驱动测试

class TestParser : public QObject {
    Q_OBJECT
private slots:
    void testHexToInt_data() {
        QTest::addColumn<QString>("hex");
        QTest::addColumn<int>("expected");

        QTest::newRow("zero")  << "00" << 0;
        QTest::newRow("ten")   << "0A" << 10;
        QTest::newRow("max")   << "FF" << 255;
        QTest::newRow("mixed") << "A5" << 165;
    }

    void testHexToInt() {
        QFETCH(QString, hex);
        QFETCH(int, expected);
        QCOMPARE(hexToInt(hex), expected);
    }
};
// 4 行测试数据 → 自动生成 4 个独立测试用例

GUI 测试

class TestMainWindow : public QObject {
    Q_OBJECT
private slots:
    void testButtonClick() {
        MainWindow w;
        w.show();

        // 查找控件
        auto *btn = w.findChild<QPushButton *>("submitBtn");
        QVERIFY(btn != nullptr);

        // 模拟点击
        QTest::mouseClick(btn, Qt::LeftButton);

        // 验证结果
        auto *label = w.findChild<QLabel *>("resultLabel");
        QCOMPARE(label->text(), QString("提交成功"));
    }

    void testKeyInput() {
        QLineEdit edit;
        QTest::keyClicks(&edit, "hello");
        QCOMPARE(edit.text(), QString("hello"));

        QTest::keyClick(&edit, Qt::Key_Return);
        // 测试回车事件
    }
};

信号测试

void testSignalEmission() {
    Worker worker;

    QSignalSpy spy(&worker, &Worker::finished);
    QVERIFY(spy.isValid());

    worker.start();
    QVERIFY(spy.wait(1000));  // 等 1 秒
    QCOMPARE(spy.count(), 1);  // 信号发射 1 次

    // 检查信号参数
    QList<QVariant> args = spy.takeFirst();
    QCOMPARE(args.at(0).toInt(), 42);  // 第一个参数
}

项目组织

tests/
├── CMakeLists.txt
├── tst_math.cpp         # 纯逻辑测试
├── tst_protocol.cpp     # 协议解析测试
├── tst_database.cpp     # DB 集成测试
├── tst_mainwindow.cpp   # GUI 测试
└── testdata/
    ├── frames.bin       # 测试用二进制数据
    └── fixtures.sql     # 数据库初始数据
# tests/CMakeLists.txt
find_package(Qt6 REQUIRED COMPONENTS Test)

function(add_qt_test name)
    add_executable(${name} ${name}.cpp)
    target_link_libraries(${name} PRIVATE Qt6::Test myapp_lib)
    add_test(NAME ${name} COMMAND ${name})
endfunction()

add_qt_test(tst_math)
add_qt_test(tst_protocol)
add_qt_test(tst_database)

# 运行: ctest --output-on-failure

常见模式

// 测试私有成员(加 friend)
class MyClass {
    friend class TestMyClass;  // 仅测试可以访问
    int privateMethod();
};

// 测试异步操作
void testAsync() {
    Worker w;
    QSignalSpy spy(&w, &Worker::done);
    QMetaObject::invokeMethod(&w, "start", Qt::QueuedConnection);
    QVERIFY(spy.wait(3000));
}