test - ArkWeb 测试代码

本目录包含 ArkWeb 项目的所有测试代码,包括单元测试和模糊测试。

概述

测试框架

  • 单元测试框架: gtest/gmock
  • 模糊测试框架: LLVM Fuzzer + FuzzedDataProvider
  • 测试构建系统: GN + Ninja

测试类型

test/
├── unittest/        # 单元测试 (58个测试目标)
├── fuzztest/        # 模糊测试 (196个测试目标)
└── resource/        # 测试资源文件

测试目录结构

1. unittest/ - 单元测试

单元测试用于验证代码的正确性,采用白盒测试方式。

统计数据

  • 测试目标数量: 58+
  • 测试文件数量: 数百个 .cpp 测试文件
  • 测试框架: gtest

目录组织

unittest/
├── common/                              # 测试公共代码
│   ├── nweb_create_window.cpp          # 窗口创建辅助函数
│   └── nweb_create_window.h
│
├── ohos_adapter/                        # 适配器层测试
│   ├── access_token_adapter_test/      # 访问令牌适配器测试
│   ├── audio_adapter/                  # 音频适配器测试
│   ├── camera_adapter_impl_test/       # 相机适配器测试
│   ├── cert_mgr_adapter_test/          # 证书管理器测试
│   ├── datashare_adapter_impl_test/    # 数据共享适配器测试
│   ├── display_adapter/                # 显示适配器测试
│   ├── event_handler_adapter_test/     # 事件处理器测试
│   ├── keystore_adapter_impl_test/     # 密钥存储测试
│   ├── location_adapter/               # 位置服务测试
│   ├── media_adapter_test/             # 媒体适配器测试
│   ├── pasteboard_adapter/             # 剪贴板测试
│   ├── power_mgr_adapter/              # 电源管理测试
│   ├── print_manager_adapter_impl_test/# 打印管理测试
│   ├── screen_capture_adapter_impl_test/# 屏幕捕获测试
│   └── ...                             # 更多适配器测试
│
├── napi_common_test/                    # NAPI 通用测试
├── napi_web_cookie_manager_test/       # NAPI Cookie 管理测试
├── native_common_test/                  # Native 通用测试
├── interface_native_test/               # Native 接口测试
│
├── nweb_helper_test/                    # NWeb 辅助类测试
├── nweb_config_helper_test/             # NWeb 配置测试
├── nweb_hisysevent_test/                # HiSysEvent 测试
├── nweb_surface_adapter_test/           # Surface 适配器测试
├── nweb_value_test/                     # NWeb 值类型测试
│
├── ohos_adapter_helper/                 # 适配器辅助类测试
├── aafwk_app_client_adapter/            # AAfwk 客户端适配器测试
├── app_fwk_update_client_test/          # 应用框架更新测试
├── ark_hisysevent_adapter_test/         # Ark HiSysEvent 测试
├── arkweb_utils_test/                   # ArkWeb 工具测试
├── battery_mgr_adapter_impl/            # 电池管理测试
├── display_manager_adapter_impl_test/   # 显示管理测试
├── mmi_adapter/                         # MMI 输入适配器测试
├── net_connect_adapter_impl_test/       # 网络连接测试
├── power_mgr_client_adapter_impl_test/  # 电源管理客户端测试
├── soc_perf_adapter_test/               # SoC 性能测试
│
├── sa_web_native_messaging_common_test/ # 系统服务消息测试
├── web_native_messaging_client_test/    # Web 消息客户端测试
└── webview_value_test/                  # WebView 值测试

单元测试命名规范

  • 目录命名: <模块名>_test<模块名>_impl_test
  • 文件命名: <模块名>_test.cpp
  • 测试类命名: <模块名>Test
  • 测试用例命名: TEST_F(<测试类名>, <测试场景>)HWTEST_F(<测试类名>, <测试场景>)

单元测试示例

测试文件: audio_adapter_impl_test/audio_adapter_impl_test.cpp

#include <gtest/gtest.h>
#include "audio_adapter_impl.h"

namespace OHOS::NWeb {

class AudioAdapterImplTest : public ::testing::Test {
protected:
    void SetUp() override {
        // 测试前初始化
        adapter_ = std::make_shared<AudioAdapterImpl>();
    }

    void TearDown() override {
        // 测试后清理
        adapter_ = nullptr;
    }

    std::shared_ptr<AudioAdapterImpl> adapter_;
};

TEST_F(AudioAdapterImplTest, InitAudioRenderer) {
    // 测试音频渲染器初始化
    EXPECT_NE(adapter_, nullptr);
    bool result = adapter_->Init();
    EXPECT_TRUE(result);
}

HWTEST_F(AudioAdapterImplTest, PlayAudio, TestSize.Level1) {
    // 测试音频播放 (需要硬件)
    bool result = adapter_->Play();
    EXPECT_TRUE(result);
}

} // namespace OHOS::NWeb

BUILD.gn: audio_adapter_impl_test/BUILD.gn

import("../../../../config.gni")
import("//build/test.gni")

module_output_path = "webview/Multimedia"

config("module_private_config") {
  if (webview_audio_enable) {
    defines = [ "NWEB_AUDIO_ENABLE" ]
  }
}

ohos_unittest("nweb_audio_adapter_test") {
  module_out_path = module_output_path

  sources = [ "audio_adapter_impl_test.cpp" ]

  configs = [ ":module_private_config" ]

  deps = [ "$webview_path/ohos_adapter:nweb_ohos_adapter" ]

  external_deps = [
    "c_utils:utils",
    "googletest:gtest_main",
    "hilog:libhilog",
  ]

  if (webview_audio_enable) {
    external_deps += [
      "audio_framework:audio_client",
      "audio_framework:audio_renderer",
    ]
  }
}

group("unittest") {
  testonly = true
  deps = []
  if (webview_audio_enable) {
    deps += [ ":nweb_audio_adapter_test" ]
  }
}

2. fuzztest/ - 模糊测试

模糊测试用于发现内存错误、崩溃、安全漏洞等问题。

统计数据

  • Fuzzer 目标数量: 196+
  • 测试框架: LLVM Fuzzer
  • 辅助工具: FuzzedDataProvider

目录组织

fuzztest/
├── ohos_adapter/                        # 适配器层模糊测试
│   ├── accesstoken_fuzzer/             # 访问令牌 Fuzzer
│   ├── ashmemcreate_fuzzer/            # Ashmem 创建 Fuzzer
│   ├── audio_adapter/                  # 音频适配器 Fuzzer
│   ├── backgroundtaskimpl_fuzzer/      # 后台任务 Fuzzer
│   ├── battery_adapter/                # 电池适配器 Fuzzer
│   ├── camera_adapter/                 # 相机适配器 Fuzzer
│   ├── cert_mgr_adapter/               # 证书管理 Fuzzer
│   ├── certmgr_fuzzer/                 # 证书管理器 Fuzzer
│   ├── datashareadapter_fuzzer/        # 数据共享 Fuzzer
│   ├── datetimeformatadapter_fuzzer/   # 日期时间格式 Fuzzer
│   ├── display_adapter/                # 显示适配器 Fuzzer
│   ├── drawingtextadapter_fuzzer/      # 绘制文本 Fuzzer
│   ├── enterprisedevicemanagementadapter_fuzzer/ # 企业设备管理 Fuzzer
│   ├── eventhandler_fuzzer/            # 事件处理器 Fuzzer
│   ├── flowbufferadapter_fuzzer/       # 流缓冲 Fuzzer
│   ├── graphic_adapter/                # 图形适配器 Fuzzer
│   ├── keystore_adapter/               # 密钥存储 Fuzzer
│   ├── location_adapter/               # 位置服务 Fuzzer
│   ├── media_adapter/                  # 媒体适配器 Fuzzer
│   ├── nativebufferadapter_fuzzer/     # Native 缓冲 Fuzzer
│   ├── nativeimage_fuzzer/             # Native 图像 Fuzzer
│   ├── ohosimagedecoder_fuzzer/        # 图像解码器 Fuzzer
│   ├── pasteboard_adapter/             # 剪贴板 Fuzzer
│   ├── power_mgr_adapter/              # 电源管理 Fuzzer
│   ├── printmanager_fuzzer/            # 打印管理 Fuzzer
│   ├── screencaptureadapterimpl_fuzzer/# 屏幕捕获 Fuzzer
│   ├── sensoradapter_fuzzer/           # 传感器 Fuzzer
│   ├── systemproperties_fuzzer/        # 系统属性 Fuzzer
│   └── ...                             # 更多适配器 Fuzzer
│
├── ohos_nweb/                          # 核心引擎模糊测试
│   ├── copyframe_fuzzer/               # 复制帧 Fuzzer
│   ├── flushbuffer_fuzzer/             # 刷新缓冲 Fuzzer
│   ├── getcookiemanager_fuzzer/        # Cookie 管理器 Fuzzer
│   ├── getdatabase_fuzzer/             # 数据库 Fuzzer
│   ├── getnweb_fuzzer/                 # NWeb 获取 Fuzzer
│   ├── getwebstorage_fuzzer/           # Web 存储 Fuzzer
│   ├── hisysevent_fuzzer/              # HiSysEvent Fuzzer
│   ├── initandrun_fuzzer/              # 初始化运行 Fuzzer
│   ├── loadlib_fuzzer/                 # 加载库 Fuzzer
│   ├── napi_fuzzer/                    # NAPI Fuzzer
│   ├── napi_webview_controller_fuzzer/ # NAPI WebViewController Fuzzer
│   ├── nwebhelper_fuzzer/              # NWebHelper Fuzzer
│   ├── requestbuffer_fuzzer/           # 请求缓冲 Fuzzer
│   ├── selectcertrequest_fuzzer/       # 选择证书请求 Fuzzer
│   ├── sethttpdns_fuzzer/              # 设置 HTTP DNS Fuzzer
│   └── setwebdebug_fuzzer/             # 设置 Web 调试 Fuzzer
│
├── arkwebutils_fuzzer/                 # ArkWeb 工具 Fuzzer
├── cannavigatebackorforward_fuzzer/    # 导航 Fuzzer
├── executejavascript_fuzzer/           # 执行 JS Fuzzer
├── executejavascriptcode_fuzzer/       # 执行 JS 代码 Fuzzer
├── load_fuzzer/                        # 加载 Fuzzer
├── loadurl_fuzzer/                     # 加载 URL Fuzzer
├── loadwithdata_fuzzer/                # 加载数据 Fuzzer
├── loadwithdataandbaseurl_fuzzer/      # 加载数据和基础 URL Fuzzer
├── ontouchmove_fuzzer/                 # 触摸移动 Fuzzer
├── ontouchpress_fuzzer/                # 触摸按下 Fuzzer
├── ontouchrelease_fuzzer/              # 触摸释放 Fuzzer
├── putbackgroundcolor_fuzzer/          # 设置背景色 Fuzzer
├── registerarkjsfunction_fuzzer/       # 注册 ArkJS 函数 Fuzzer
├── registerasyncjsproxy_fuzzer/        # 注册异步 JS 代理 Fuzzer
├── sendkeyevent_fuzzer/                # 发送按键事件 Fuzzer
├── unregisterarkjsfunction_fuzzer/     # 注销 ArkJS 函数 Fuzzer
├── verifypackageinstall_fuzzer/        # 验证包安装 Fuzzer
└── zoom_fuzzer/                        # 缩放 Fuzzer

模糊测试结构

每个 Fuzzer 目录包含:

loadurl_fuzzer/
├── BUILD.gn           # 构建配置
├── project.xml        # Fuzzer 配置
├── corpus/            # 语料库 (种子输入)
├── loadurl_fuzzer.h   # 头文件
└── loadurl_fuzzer.cpp # Fuzzer 实现

project.xml - Fuzzer 配置文件:

<fuzz_config>
  <fuzztest>
    <!-- 测试输入的最大长度 -->
    <max_len>1000</max_len>
    <!-- Fuzzer 运行的最长时间(秒) -->
    <max_total_time>300</max_total_time>
    <!-- 内存使用限制(MB) -->
    <rss_limit_mb>4096</rss_limit_mb>
  </fuzztest>
</fuzz_config>

Fuzzer 实现:

#include "loadurl_fuzzer.h"
#include <fuzzer/FuzzedDataProvider.h>
#include "nweb.h"
#include "nweb_create_window.h"

namespace OHOS {
constexpr uint8_t MAX_STRING_LENGTH = 64;
constexpr int MAX_VECTOR_SIZE = 10;

std::shared_ptr<OHOS::NWeb::NWeb> g_nweb = nullptr;

bool LoadUrlFuzzTest(const uint8_t* data, size_t size)
{
    if ((data == nullptr) || (size == 0)) {
        return true;
    }

    // 使用 FuzzedDataProvider 解析随机输入
    FuzzedDataProvider dataProvider(data, size);

    // 获取或创建测试用的 NWeb 实例
    g_nweb = NWeb::GetNwebForTest();
    if (g_nweb == nullptr) {
        return true;
    }

    // 生成随机 URL
    std::string url = dataProvider.ConsumeRandomLengthString(MAX_STRING_LENGTH);

    // 生成随机 HTTP 头
    std::map<std::string, std::string> additionalHttpHeaders;
    int map_size = dataProvider.ConsumeIntegralInRange<int>(1, MAX_VECTOR_SIZE);
    for (int i = 0; i < map_size; i++) {
        std::string key = dataProvider.ConsumeRandomLengthString(MAX_STRING_LENGTH);
        std::string value = dataProvider.ConsumeRandomLengthString(MAX_STRING_LENGTH);
        additionalHttpHeaders[key] = value;
    }

    // 调用被测试函数
    g_nweb->Load(url, additionalHttpHeaders);

    return true;
}
} // namespace OHOS

/* Fuzzer 入口点 */
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
    OHOS::LoadUrlFuzzTest(data, size);
    return 0;
}

BUILD.gn:

import("//build/test.gni")

ohos_fuzztest("LoadUrlFuzzTest") {
  module_out_path = webview_fuzz_test_path
  fuzz_config_file = "$webview_path/test/fuzztest/loadurl_fuzzer"

  include_dirs = [
    "$webview_path/test/ohos_nweb",
    "$webview_path/test/unittest/common",
  ]

  cflags = [
    "-g",
    "-O0",  # 不优化以便调试
    "-Wno-unused-variable",
    "-fno-omit-frame-pointer",
  ]

  sources = [
    "../../unittest/common/nweb_create_window.cpp",
    "loadurl_fuzzer.cpp",
  ]

  external_deps = [
    "c_utils:utils",
    "hilog:libhilog",
    "webview:libnweb",
  ]
}

group("fuzztest") {
  testonly = true
  deps = [ ":LoadUrlFuzzTest" ]
}

3. resource/ - 测试资源

测试使用的资源文件:

resource/
└── image/
    └── images/    # 测试用图片资源

工作原理

单元测试工作流程

1. 编写测试代码
   ↓
2. 编写 BUILD.gn
   ↓
3. GN 构建测试目标
   ↓
4. Ninja 编译测试二进制
   ↓
5. 运行测试
   ↓
6. 收集测试结果
   ↓
7. 生成测试报告

1. 测试代码结构

// 1. 包含必要的头文件
#include <gtest/gtest.h>
#include "被测试的类.h"

// 2. 定义测试固件 (Test Fixture)
class MyTest : public ::testing::Test {
protected:
    void SetUp() override {
        // 测试前初始化
        obj_ = new MyObject();
    }

    void TearDown() override {
        // 测试后清理
        delete obj_;
    }

    MyObject* obj_;
};

// 3. 编写测试用例
TEST_F(MyTest, NormalOperation) {
    EXPECT_NE(obj_, nullptr);
    EXPECT_TRUE(obj_->DoSomething());
}

// 4. 对于需要硬件的测试,使用 HWTEST_F
HWTEST_F(MyTest, HardwareRequired, TestSize.Level1) {
    // 需要硬件的测试
    EXPECT_TRUE(obj_->AccessHardware());
}

2. 条件编译

测试代码支持条件编译,与主代码保持一致:

// BUILD.gn
config("module_private_config") {
  if (webview_audio_enable) {
    defines = [ "NWEB_AUDIO_ENABLE" ]
  }
}

// 测试代码
#if defined(NWEB_AUDIO_ENABLE)
TEST_F(AudioTest, PlayAudio) {
    // 仅在音频启用时编译
    audio_adapter_->Play();
}
#endif

3. Mock 实现

对于不可用的功能,使用 Mock 实现:

#ifdef NWEB_AUDIO_ENABLE
    // 真实实现
    auto adapter = std::make_shared<AudioAdapterImpl>();
#else
    // Mock 实现
    auto adapter = std::make_shared<AudioAdapterMock>();
#endif

模糊测试工作流程

1. Fuzzer 生成随机输入
   ↓
2. LLVMFuzzerTestOneInput 接收输入
   ↓
3. FuzzedDataProvider 解析输入
   ↓
4. 调用被测试函数
   ↓
5. 检测崩溃/内存错误
   ↓
6. 保存失败用例到 corpus
   ↓
7. 使用失败用例重新生成输入

FuzzedDataProvider 使用

FuzzedDataProvider 是辅助工具,用于将原始字节转换为有意义的输入:

FuzzedDataProvider dataProvider(data, size);

// 消费随机长度字符串
std::string str = dataProvider.ConsumeRandomLengthString(MAX_LEN);

// 消费固定长度字符串
std::string fixedStr = dataProvider.ConsumeBytes(10);

// 消费整数
int intValue = dataProvider.ConsumeIntegral<int>();
int rangeInt = dataProvider.ConsumeIntegralInRange<int>(0, 100);

// 消费布尔值
bool flag = dataProvider.ConsumeBool();

// 消费字节
uint8_t byte = dataProvider.ConsumeIntegral<uint8_t>();

// 剩余字节数
size_t remaining = dataProvider.remaining_bytes();

构建和运行测试

构建测试

1. 构建所有单元测试

# 在 OpenHarmony 代码仓根目录执行
./build.sh --product-name rk3568 --build-target //base/web/webview/test:unittest

2. 构建单个测试

# 构建音频适配器测试
./build.sh --product-name rk3568 \
  --build-target //base/web/webview/test/unittest/audio_adapter_impl_test:unittest

3. 构建所有模糊测试

./build.sh --product-name rk3568 --build-target //base/web/webview/test:fuzztest

4. 构建单个 Fuzzer

./build.sh --product-name rk3568 \
  --build-target //base/web/webview/test/fuzztest/loadurl_fuzzer:fuzztest

运行测试

1. 运行单元测试

# 方法 1: 使用 runtest
runtest --part-name webview --test-case nweb_audio_adapter_test

# 方法 2: 直接执行测试二进制
hdc shell mount -o remount,rw /
hdc shell push out/rk3568/tests/unittest/webview/Multimedia/nweb_audio_adapter_test /data/local/tmp/
hdc shell chmod +x /data/local/tmp/nweb_audio_adapter_test
hdc shell /data/local/tmp/nweb_audio_adapter_test

# 方法 3: 使用 hdc shell
hdc shell
cd /data/local/tmp/
./nweb_audio_adapter_test

2. 运行模糊测试

# 方法 1: 使用 runtest
runtest --part-name webview --test-case LoadUrlFuzzTest --fuzztest

# 方法 2: 直接执行 Fuzzer
hdc shell push out/rk3568/tests/fuzztest/webview/LoadUrlFuzzTest /data/local/tmp/
hdc shell chmod +x /data/local/tmp/LoadUrlFuzzTest

# 运行 Fuzzer (可以指定时间或 corpus)
hdc shell /data/local/tmp/LoadUrlFuzzTest -max_total_time=60

# 使用 corpus 重新运行
hdc shell mkdir /data/local/tmp/corpus
hdc shell push corpus/* /data/local/tmp/corpus/
hdc shell /data/local/tmp/LoadUrlFuzzTest /data/local/tmp/corpus

3. 查看测试结果

# 单元测试输出
# 运行测试后会生成报告
# ========== Test Summary ==========
# Total Tests: 150
# Passed: 148
# Failed: 2
# Skipped: 0

# 模糊测试输出
# Fuzzer 会显示发现的崩溃
# INFO: Seed: 1234567890
# INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 1000 bytes
# INFO: A corpus is not provided, starting from an empty corpus
# #2      INITED cov: 12 ft: 12 corp: 1/1b exec/s: 0 rss: 25Mb
# #1024   NEW    cov: 15 ft: 15 corp: 2/3b lim: 8 exec/s: 0 rss: 25Mb

测试覆盖范围

适配器层测试 (ohos_adapter)

覆盖以下适配器:

  • 多媒体: audio, camera, media, av_codec, av_session
  • 网络: net_connect, net_proxy
  • 位置: location
  • 电源: battery_manager, power_manager
  • 显示: display_manager, graphic, surface
  • 输入: mmi, imf
  • 存储: database, keystore, share_preferences
  • 剪贴板: pasteboard
  • 打印: print_manager
  • 证书: cert_manager
  • 传感器: sensor
  • 时间: date_time_format
  • 事件: event_handler, hisysevent
  • 其他: access_token, background, data_share, enterprise, screen_capture, screen_lock, soc_perf, system_properties

核心引擎测试 (ohos_nweb)

  • NWebHelper: 引擎初始化、配置
  • NWeb: 核心功能 (Load, JavaScript, GoBack, Forward 等)
  • CookieManager: Cookie 管理
  • WebStorage: Web 存储
  • Database: 数据库访问
  • HiSysEvent: 系统事件上报

接口层测试

  • NAPI 接口: JavaScript 绑定测试
  • Native 接口: C++ NDK 接口测试
  • NativeCommon: 通用组件测试

系统服务测试

  • app_fwk_update: 应用框架更新
  • web_native_messaging: Web 消息服务

测试最佳实践

1. 测试命名

目录命名

  • 使用描述性名称
  • 格式: <模块名>_test<模块名>_impl_test
  • 示例: audio_adapter_impl_test, nweb_helper_test

测试类命名

  • 格式: <被测试类>Test
  • 示例: AudioAdapterImplTest, NWebHelperTest

测试用例命名

  • 格式: TEST_F(<测试类名>, <测试场景>)
  • 描述性命名,说明测试什么
  • 示例:
    • TEST_F(AudioTest, PlayAudio)
    • TEST_F(NWebTest, LoadUrlWithInvalidUrl)
    • TEST_F(CookieTest, SetCookieWithExpiry)

2. 测试结构

class MyTest : public ::testing::Test {
protected:
    void SetUp() override {
        // 1. 初始化被测试对象
        obj_ = std::make_shared<MyClass>();

        // 2. 准备测试数据
        test_data_ = "test";
    }

    void TearDown() override {
        // 3. 清理资源
        obj_.reset();
    }

    // 4. 测试辅助方法
    void PrepareTestEnvironment();

    std::shared_ptr<MyClass> obj_;
    std::string test_data_;
};

TEST_F(MyTest, NormalCase) {
    // Arrange (准备)
    std::string input = "valid_input";

    // Act (执行)
    bool result = obj_->Process(input);

    // Assert (断言)
    EXPECT_TRUE(result);
}

3. 断言使用

EXPECT_* vs ASSERT_*

// EXPECT_*: 失败后继续执行
EXPECT_EQ(obj_->GetValue(), 10);
EXPECT_TRUE(obj_->IsValid());

// ASSERT_*: 失败后立即停止
ASSERT_NE(obj_, nullptr);  // 确保对象有效
obj_->DoSomething();

常用断言

// 布尔值
EXPECT_TRUE(condition);
EXPECT_FALSE(condition);

// 相等性
EXPECT_EQ(expected, actual);
EXPECT_NE(val1, val2);

// 比较
EXPECT_LT(val1, val2);  // Less than
EXPECT_LE(val1, val2);  // Less or equal
EXPECT_GT(val1, val2);  // Greater than
EXPECT_GE(val1, val2);  // Greater or equal

// 指针
EXPECT_NE(ptr, nullptr);
EXPECT_EQ(ptr, expected_ptr);

// 浮点数
EXPECT_FLOAT_EQ(float1, float2);
EXPECT_NEAR(val1, val2, 0.001);  // 允许误差

// 字符串
EXPECT_STREQ(str1, str2);
EXPECT_STRCASEEQ(str1, str2);  // 忽略大小写

4. 测试覆盖

正常路径

TEST_F(MyTest, NormalOperation) {
    EXPECT_TRUE(obj_->DoSomething("valid_input"));
}

边界条件

TEST_F(MyTest, BoundaryConditions) {
    // 空输入
    EXPECT_FALSE(obj_->DoSomething(""));

    // 最大值
    EXPECT_TRUE(obj_->SetValue(INT_MAX));

    // 最小值
    EXPECT_TRUE(obj_->SetValue(INT_MIN));
}

异常情况

TEST_F(MyTest, ErrorHandling) {
    // 无效输入
    EXPECT_FALSE(obj_->DoSomething(nullptr));

    // 超出范围
    EXPECT_FALSE(obj_->SetValue(INT_MAX + 1));
}

5. Mock 使用

对于依赖外部系统的功能,使用 Mock:

class MockAudioSystem : public AudioSystem {
public:
    MOCK_METHOD(bool, Play, (), (override));
    MOCK_METHOD(void, Stop, (), (override));
};

TEST_F(AudioTest, UsingMock) {
    auto mockAudio = std::make_shared<MockAudioSystem>();

    // 设置预期行为
    EXPECT_CALL(*mockAudio, Play())
        .Times(1)
        .WillOnce(Return(true));

    // 使用 Mock 测试
    audio_adapter_->SetAudioSystem(mockAudio);
    EXPECT_TRUE(audio_adapter_->PlayAudio());
}

6. 参数化测试

使用相同逻辑测试多组数据:

class LoadUrlTest : public ::testing::TestWithParam<std::string> {
protected:
    std::shared_ptr<NWeb> nweb_;
};

TEST_P(LoadUrlTest, LoadVariousUrls) {
    std::string url = GetParam();
    EXPECT_TRUE(nweb_->Load(url));
}

INSTANTIATE_TEST_SUITE_P(
    ValidUrls,
    LoadUrlTest,
    ::testing::Values(
        "https://www.example.com",
        "http://test.org",
        "file:///data/local/file.html"
    )
);

7. 条件编译测试

确保测试代码与主代码同步:

// BUILD.gn
config("module_private_config") {
  if (webview_audio_enable) {
    defines = [ "NWEB_AUDIO_ENABLE" ]
  }
}

// 测试代码
#if defined(NWEB_AUDIO_ENABLE)
TEST_F(AudioTest, PlayAudio) {
    EXPECT_TRUE(audio_adapter_->Play());
}
#endif

TEST_F(AudioTest, CheckAvailability) {
#ifdef NWEB_AUDIO_ENABLE
    EXPECT_TRUE(audio_adapter_->IsAvailable());
#else
    EXPECT_FALSE(audio_adapter_->IsAvailable());
#endif
}

8. 测试隔离

每个测试应该独立:

TEST_F(MyTest, Test1) {
    // 不要依赖 Test2 的执行顺序
    // 不要依赖 Test2 修改的状态
    // 每个测试在 SetUp 中初始化,在 TearDown 中清理
}

9. 资源清理

确保资源正确释放:

class MyTest : public ::testing::Test {
protected:
    void TearDown() override {
        // 清理文件
        if (access(test_file_.c_str(), F_OK) == 0) {
            unlink(test_file_.c_str());
        }

        // 清理对象
        obj_.reset();

        // 断开连接
        if (connection_) {
            connection_->Close();
        }
    }
};

10. 测试性能

对于性能关键代码,使用性能测试:

TEST_F(PerformanceTest, LoadUrlPerformance) {
    auto start = std::chrono::high_resolution_clock::now();

    for (int i = 0; i < 100; i++) {
        nweb_->Load("https://example.com");
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    EXPECT_LT(duration.count(), 5000);  // 100次加载应在5秒内完成
}

调试测试

1. 启用详细输出

# 运行测试时启用详细输出
hdc shell /data/local/tmp/my_test --gtest_verbose=1

# 仅运行特定测试
hdc shell /data/local/tmp/my_test --gtest_filter=MyTest.TestName

2. 调试模式构建

# 构建 Debug 版本
./build.sh --product-name rk3568 \
  --build-target //base/web/webview/test/unittest/my_test:unittest \
  --gn-args="is_debug=true"

3. 使用日志

#include "hilog/log.h"

TEST_F(MyTest, DebugTest) {
    WVLOG_I("Starting test");

    bool result = obj_->DoSomething();
    WVLOG_I("Result: %{public}d", result);

    EXPECT_TRUE(result);
}

4. GDB 调试

# 在设备上使用 gdb
hdc shell
gdb /data/local/tmp/my_test
(gdb) break MyTest::TestName
(gdb) run

常见问题

Q1: 测试编译失败,找不到依赖?

原因: BUILD.gn 中未声明依赖或 external_deps

解决:

deps = [
  "$webview_path/ohos_adapter:nweb_ohos_adapter",
]

external_deps = [
  "c_utils:utils",
  "hilog:libhilog",
  "googletest:gtest_main",
]

Q2: 测试运行时崩溃?

原因: 未初始化、空指针、依赖不可用

解决:

  • 确保 SetUp 中正确初始化
  • 使用 #ifdef 检查功能可用性
  • 查看 hilog -T ArkWeb 日志

Q3: HWTEST_F 测试跳过?

原因: 缺少硬件支持

解决:

  • 使用 --gtest_also_run_disabled_tests 强制运行
  • 或在真实设备上运行

Q4: Fuzzer 内存不足?

原因: rss_limit_mb 限制太低

解决: 修改 project.xml

<rss_limit_mb>8192</rss_limit_mb>

Q5: 如何跳过某个测试?

TEST_F(MyTest, DisabledTest) {
    GTEST_SKIP() << "Skipping this test";
    // 不会执行
}

// 运行时跳过
hdc shell /data/local/tmp/my_test --gtest_filter=-MyTest.DisabledTest

相关文档

测试命令速查

# 构建所有单元测试
./build.sh --product-name rk3568 --build-target //base/web/webview/test:unittest

# 构建所有模糊测试
./build.sh --product-name rk3568 --build-target //base/web/webview/test:fuzztest

# 运行单元测试
runtest --part-name webview --test-case nweb_audio_adapter_test

# 运行模糊测试
runtest --part-name webview --test-case LoadUrlFuzzTest --fuzztest

# 查看测试日志
hilog -T ArkWeb

# 列出所有测试
hdc shell /data/local/tmp/my_test --gtest_list_tests