问题思维导图

              调试与测试
                 |
        ┌────────┼────────┐
        |        |        |
    调试问题  测试问题  性能问题
        |        |        |
    ┌───┴───┐┌───┴───┐┌───┴───┐
    |       ||       ||       |
  日志  断点  单元  集成  内存  CPU
  不足  失效  测试  测试  泄漏  占用

问题一:混合应用的调试困难

问题描述

在Qt与鸿蒙原生的混合应用中,调试面临独特的挑战:

  • 无法同时调试两个运行时
  • 崩溃堆栈信息不完整
  • 跨语言调用链难以追踪
  • 异步操作难以调试

调试流程图

应用崩溃
    |
    ├─→ 获取崩溃堆栈 ──→ 分析堆栈 ──→ 定位问题
    |                                  |
    |                                  ├─→ Qt侧问题 ──→ 使用Qt调试器
    |                                  |
    |                                  └─→ JNI侧问题 ──→ 使用logcat
    |
    ├─→ 添加日志 ──→ 重现问题 ──→ 查看日志 ──→ 定位问题
    |
    └─→ 使用断点 ──→ 单步执行 ──→ 观察变量 ──→ 定位问题

调试工具链逻辑图

调试工具体系
    |
    ├─→ Qt调试器 (GDB/LLDB)
    |   ├─→ 断点设置
    |   ├─→ 单步执行
    |   ├─→ 变量观察
    |   └─→ 内存检查
    |
    ├─→ 日志系统
    |   ├─→ qDebug() 输出
    |   ├─→ 文件日志
    |   ├─→ 日志级别
    |   └─→ 日志过滤
    |
    ├─→ 性能分析
    |   ├─→ 内存分析
    |   ├─→ CPU分析
    |   ├─→ 网络分析
    |   └─→ 磁盘分析
    |
    └─→ 远程调试
        ├─→ TCP连接
        ├─→ 日志流
        └─→ 命令执行

解决方案:统一调试框架

// debug_framework.h
#include <QString>
#include <QObject>
#include <functional>

enum class DebugLevel {
    VERBOSE,
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    CRITICAL
};

class DebugFramework : public QObject {
    Q_OBJECT

public:
    static DebugFramework &instance();
    
    // 设置调试级别
    void setDebugLevel(DebugLevel level);
    
    // 记录调试信息
    void log(DebugLevel level, const QString &tag, const QString &message);
    
    // 便捷方法
    void verbose(const QString &tag, const QString &message);
    void debug(const QString &tag, const QString &message);
    void info(const QString &tag, const QString &message);
    void warning(const QString &tag, const QString &message);
    void error(const QString &tag, const QString &message);
    void critical(const QString &tag, const QString &message);
    
    // 记录函数调用
    void logFunctionEntry(const QString &functionName);
    void logFunctionExit(const QString &functionName, long long durationMs);
    
    // 记录异常
    void logException(const QString &context, const std::exception &e);
    
    // 获取调试信息
    QString getDebugInfo() const;
    
    // 导出日志
    bool exportLogs(const QString &filePath);
    
    // 启用/禁用调试
    void setEnabled(bool enabled);

signals:
    void logMessage(DebugLevel level, const QString &tag, const QString &message);

private:
    DebugFramework();
    
    struct LogEntry {
        DebugLevel level;
        QString tag;
        QString message;
        long long timestamp;
    };
    
    DebugLevel m_debugLevel;
    QList<LogEntry> m_logHistory;
    bool m_enabled;
    QMutex m_mutex;
    static const int MAX_LOG_ENTRIES = 10000;
};

文字解释:

这个调试框架提供了统一的日志记录接口。通过setDebugLevel()可以控制日志详细程度。logFunctionEntry()logFunctionExit()可以追踪函数调用和执行时间。logException()捕获异常信息。exportLogs()可以将日志导出到文件用于离线分析。通过信号机制,可以实时监控日志输出。

// debug_framework.cpp
#include "debug_framework.h"
#include <QDateTime>
#include <QFile>
#include <QDebug>

DebugFramework &DebugFramework::instance()
{
    static DebugFramework framework;
    return framework;
}

DebugFramework::DebugFramework()
    : m_debugLevel(DebugLevel::DEBUG), m_enabled(true)
{
}

void DebugFramework::setDebugLevel(DebugLevel level)
{
    m_debugLevel = level;
}

void DebugFramework::log(DebugLevel level, const QString &tag, const QString &message)
{
    if (!m_enabled || level < m_debugLevel) {
        return;
    }
    
    QMutexLocker locker(&m_mutex);
    
    LogEntry entry;
    entry.level = level;
    entry.tag = tag;
    entry.message = message;
    entry.timestamp = QDateTime::currentMSecsSinceEpoch();
    
    m_logHistory.append(entry);
    
    // 限制日志数量
    if (m_logHistory.size() > MAX_LOG_ENTRIES) {
        m_logHistory.removeFirst();
    }
    
    // 输出到调试器
    QString levelStr;
    switch (level) {
    case DebugLevel::VERBOSE:
        levelStr = "V";
        break;
    case DebugLevel::DEBUG:
        levelStr = "D";
        break;
    case DebugLevel::INFO:
        levelStr = "I";
        break;
    case DebugLevel::WARNING:
        levelStr = "W";
        break;
    case DebugLevel::ERROR:
        levelStr = "E";
        break;
    case DebugLevel::CRITICAL:
        levelStr = "C";
        break;
    }
    
    QString logMessage = QString("[%1] [%2] %3: %4")
                        .arg(QDateTime::currentDateTime().toString("hh:mm:ss.zzz"))
                        .arg(levelStr)
                        .arg(tag)
                        .arg(message);
    
    qDebug().noquote() << logMessage;
    
    emit logMessage(level, tag, message);
}

void DebugFramework::verbose(const QString &tag, const QString &message)
{
    log(DebugLevel::VERBOSE, tag, message);
}

void DebugFramework::debug(const QString &tag, const QString &message)
{
    log(DebugLevel::DEBUG, tag, message);
}

void DebugFramework::info(const QString &tag, const QString &message)
{
    log(DebugLevel::INFO, tag, message);
}

void DebugFramework::warning(const QString &tag, const QString &message)
{
    log(DebugLevel::WARNING, tag, message);
}

void DebugFramework::error(const QString &tag, const QString &message)
{
    log(DebugLevel::ERROR, tag, message);
}

void DebugFramework::critical(const QString &tag, const QString &message)
{
    log(DebugLevel::CRITICAL, tag, message);
}

void DebugFramework::logFunctionEntry(const QString &functionName)
{
    debug("FUNCTION", QString(">>> %1").arg(functionName));
}

void DebugFramework::logFunctionExit(const QString &functionName, long long durationMs)
{
    debug("FUNCTION", QString("<<< %1 (%2ms)").arg(functionName).arg(durationMs));
}

void DebugFramework::logException(const QString &context, const std::exception &e)
{
    error("EXCEPTION", QString("%1: %2").arg(context, e.what()));
}

QString DebugFramework::getDebugInfo() const
{
    QMutexLocker locker(&m_mutex);
    
    QString info = "=== Debug Information ===\n";
    info += QString("Total log entries: %1\n").arg(m_logHistory.size());
    info += QString("Debug level: %1\n\n").arg(static_cast<int>(m_debugLevel));
    
    info += "Recent logs:\n";
    int startIndex = qMax(0, m_logHistory.size() - 20);
    for (int i = startIndex; i < m_logHistory.size(); ++i) {
        const LogEntry &entry = m_logHistory[i];
        info += QString("[%1] %2: %3\n")
               .arg(entry.tag)
               .arg(static_cast<int>(entry.level))
               .arg(entry.message);
    }
    
    return info;
}

bool DebugFramework::exportLogs(const QString &filePath)
{
    QMutexLocker locker(&m_mutex);
    
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qWarning() << "Failed to open log file:" << filePath;
        return false;
    }
    
    for (const LogEntry &entry : m_logHistory) {
        QString line = QString("[%1] [%2] %3: %4\n")
                      .arg(QDateTime::fromMSecsSinceEpoch(entry.timestamp).toString())
                      .arg(static_cast<int>(entry.level))
                      .arg(entry.tag)
                      .arg(entry.message);
        
        file.write(line.toUtf8());
    }
    
    file.close();
    qDebug() << "Logs exported to:" << filePath;
    return true;
}

void DebugFramework::setEnabled(bool enabled)
{
    m_enabled = enabled;
}

文字解释:

这段实现代码展示了完整的日志系统。每条日志都包含时间戳、级别、标签和消息。logFunctionEntry()logFunctionExit()用箭头符号标记函数的进入和退出,便于追踪调用流程。日志存储在内存中,最多保留10000条,防止内存溢出。exportLogs()可以将日志导出到文件用于离线分析。


问题二:单元测试与集成测试

问题描述

混合应用的测试存在复杂性:

  • 难以隔离Qt和JNI代码进行单元测试
  • 集成测试需要模拟系统环境
  • 测试覆盖率难以评估
  • 自动化测试困难

测试流程图

测试流程
    |
    ├─→ 单元测试
    |   ├─→ 测试Qt代码 ──→ 使用Qt Test框架
    |   |
    |   └─→ 测试JNI代码 ──→ 使用Mock对象
    |
    ├─→ 集成测试
    |   ├─→ 测试Qt-JNI交互 ──→ 使用测试工具
    |   |
    |   └─→ 测试系统集成 ──→ 使用真实设备
    |
    └─→ 性能测试
        ├─→ 测试内存使用 ──→ 使用内存分析工具
        |
        └─→ 测试CPU使用 ──→ 使用性能分析工具

测试架构逻辑图

测试体系
    |
    ├─→ 单元测试层
    |   ├─→ 业务逻辑测试
    |   ├─→ 工具类测试
    |   └─→ 算法测试
    |
    ├─→ 集成测试层
    |   ├─→ 模块交互测试
    |   ├─→ 系统功能测试
    |   └─→ 端到端测试
    |
    └─→ 性能测试层
        ├─→ 内存测试
        ├─→ CPU测试
        └─→ 网络测试

解决方案:测试框架

// test_framework.h
#include <QString>
#include <QObject>
#include <functional>
#include <vector>

class TestCase {
public:
    QString name;
    std::function<bool()> testFunc;
    bool passed;
    QString errorMessage;
    long long durationMs;
};

class TestFramework : public QObject {
    Q_OBJECT

public:
    static TestFramework &instance();
    
    // 注册测试用例
    void registerTestCase(const QString &name, std::function<bool()> testFunc);
    
    // 运行所有测试
    bool runAllTests();
    
    // 运行特定测试
    bool runTest(const QString &name);
    
    // 获取测试结果
    QString getTestResults() const;
    
    // 获取测试统计
    QString getTestStats() const;
    
    // 断言方法
    static bool assertEqual(const QString &expected, const QString &actual);
    static bool assertTrue(bool condition);
    static bool assertFalse(bool condition);

private:
    TestFramework();
    
    std::vector<TestCase> m_testCases;
    int m_passedCount;
    int m_failedCount;
};

文字解释:

这个测试框架提供了简单的单元测试支持。通过registerTestCase()注册测试用例,runAllTests()执行所有测试。每个测试用例都记录了执行时间和结果。assertEqual()assertTrue()等断言方法用于验证测试结果。getTestResults()getTestStats()提供详细的测试报告。

// test_framework.cpp
#include "test_framework.h"
#include <QDebug>
#include <QDateTime>

TestFramework &TestFramework::instance()
{
    static TestFramework framework;
    return framework;
}

TestFramework::TestFramework()
    : m_passedCount(0), m_failedCount(0)
{
}

void TestFramework::registerTestCase(const QString &name, std::function<bool()> testFunc)
{
    TestCase testCase;
    testCase.name = name;
    testCase.testFunc = testFunc;
    testCase.passed = false;
    testCase.durationMs = 0;
    
    m_testCases.push_back(testCase);
    
    qDebug() << "Registered test case:" << name;
}

bool TestFramework::runAllTests()
{
    qDebug() << "Running" << m_testCases.size() << "test cases...";
    
    m_passedCount = 0;
    m_failedCount = 0;
    
    for (auto &testCase : m_testCases) {
        runTest(testCase.name);
    }
    
    return m_failedCount == 0;
}

bool TestFramework::runTest(const QString &name)
{
    auto it = std::find_if(m_testCases.begin(), m_testCases.end(),
                          [&name](const TestCase &tc) { return tc.name == name; });
    
    if (it == m_testCases.end()) {
        qWarning() << "Test case not found:" << name;
        return false;
    }
    
    TestCase &testCase = *it;
    
    auto startTime = QDateTime::currentMSecsSinceEpoch();
    
    try {
        testCase.passed = testCase.testFunc();
    } catch (const std::exception &e) {
        testCase.passed = false;
        testCase.errorMessage = QString::fromStdString(e.what());
    }
    
    testCase.durationMs = QDateTime::currentMSecsSinceEpoch() - startTime;
    
    if (testCase.passed) {
        m_passedCount++;
        qDebug() << "✓ PASSED:" << name << "(" << testCase.durationMs << "ms)";
    } else {
        m_failedCount++;
        qDebug() << "✗ FAILED:" << name << "-" << testCase.errorMessage;
    }
    
    return testCase.passed;
}

QString TestFramework::getTestResults() const
{
    QString results = "=== Test Results ===\n";
    
    for (const auto &testCase : m_testCases) {
        results += QString("%1: %2 (%3ms)\n")
                  .arg(testCase.name)
                  .arg(testCase.passed ? "PASSED" : "FAILED")
                  .arg(testCase.durationMs);
        
        if (!testCase.errorMessage.isEmpty()) {
            results += QString("  Error: %1\n").arg(testCase.errorMessage);
        }
    }
    
    return results;
}

QString TestFramework::getTestStats() const
{
    QString stats = "=== Test Statistics ===\n";
    stats += QString("Total: %1\n").arg(m_passedCount + m_failedCount);
    stats += QString("Passed: %1\n").arg(m_passedCount);
    stats += QString("Failed: %1\n").arg(m_failedCount);
    
    if (m_passedCount + m_failedCount > 0) {
        float passRate = (float)m_passedCount / (m_passedCount + m_failedCount) * 100;
        stats += QString("Pass Rate: %.1f%%\n").arg(passRate);
    }
    
    return stats;
}

bool TestFramework::assertEqual(const QString &expected, const QString &actual)
{
    if (expected != actual) {
        qWarning() << "Assertion failed: expected" << expected << "but got" << actual;
        return false;
    }
    return true;
}

bool TestFramework::assertTrue(bool condition)
{
    if (!condition) {
        qWarning() << "Assertion failed: condition is false";
        return false;
    }
    return true;
}

bool TestFramework::assertFalse(bool condition)
{
    if (condition) {
        qWarning() << "Assertion failed: condition is true";
        return false;
    }
    return true;
}

文字解释:

这段实现代码展示了测试框架的完整逻辑。runTest()执行单个测试用例,记录执行时间和结果。如果测试抛出异常,会被捕获并记录。getTestResults()生成详细的测试报告,getTestStats()生成统计摘要。通过这个框架,可以轻松构建自动化测试套件。


最佳实践总结

问题 解决方案 关键点
调试困难 统一日志框架 追踪函数调用
测试困难 测试框架 自动化测试
性能分析 性能监控 及时发现瓶颈
问题诊断 日志导出 离线分析

使用示例

// 配置调试
DebugFramework::instance().setDebugLevel(DebugLevel::DEBUG);

// 记录日志
DebugFramework::instance().debug("TAG", "Debug message");
DebugFramework::instance().error("TAG", "Error message");

// 注册测试用例
TestFramework::instance().registerTestCase("test_add", []() {
    return TestFramework::assertEqual("5", "5");
});

// 运行测试
TestFramework::instance().runAllTests();

// 获取报告
qDebug() << TestFramework::instance().getTestStats();

// 导出日志
DebugFramework::instance().exportLogs("/path/to/logs.txt");

通过这些方案,可以构建一个可调试、可测试的混合应用。

欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

Logo

赋能鸿蒙PC开发者,共建全场景原生生态,共享一次开发多端部署创新价值。

更多推荐