- 更新发布版本从 0.1.0-SNAPSHOT 到 0.4.0 - 更新 README.md 中的依赖版本引用 - 完善 TEST_REPORT.md 包括最新测试结果和新增测试用例 - 添加详细的 TEST_PLAN.md 文档 - 更新 sample-app 的测试配置和依赖 - 为各个 SDK 模块添加 ProGuard 规则文件 - 修复 ApiClient 中的 Gson 类型适配器问题 - 改进测试架构,解决会话删除和跨设备测试问题
17 KiB
17 KiB
Android SDK 测试报告
生成时间: 2026-05-03(最后更新: 2026-05-04)
版本: 0.4.x(UserSig 鉴权)
测试状态: 全部通过 ✅(TC-01 ~ TC-15 + TC-99,共 17 用例)
测试环境
| 项目 | 版本/配置 |
|---|---|
| Android Gradle Plugin | 9.1.0 |
| Gradle | 8.9 |
| JDK | OpenJDK 21 |
| 模拟器 1 | emulator-5554(Pixel 9 Pro API 36) |
| 模拟器 2 | emulator-5556(Pixel 9 Pro API 36) |
| compileSdk | 36 |
| minSdk | 24(Android 7.0) |
| Kotlin | 2.3.10 |
| 测试框架 | AndroidJUnit4 + Instrumented Test |
| 后端环境 | https://dev.xuqinmin.com |
测试用例清单
TC-01 SDK 初始化测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 SDK 初始化流程及模块依赖注入 |
| 测试步骤 | 1. 在 Application.onCreate() 中调用 XuqmSDK.initialize(context, appKey, logLevel) 2. 确认 XuqmSDK.config、tokenStore 已赋值 3. 确认 ApiClient 已初始化 |
| 预期结果 | 1. 初始化成功,无异常抛出 2. XuqmSDK.requireInit() 不抛异常 3. ServiceEndpointRegistry 默认使用内置生产环境地址 |
| 实际结果 | 通过 |
| 通过状态 | ✅ |
TC-02 IM 登录/登出测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 UserSig 鉴权模式下的登录与登出 |
| 测试步骤 | 1. 调用 XuqmSDK.login(userId, userSig) 2. 观察 ImSDK.onSdkLogin 是否自动触发 WebSocket 连接 3. 监听 ImEventListener.onConnected() 4. 调用 XuqmSDK.logout() 5. 确认 ImSDK.onSdkLogout 断开 WebSocket 并清空 Token |
| 预期结果 | 1. 登录返回 XuqmLoginSession 2. WebSocket 建立 101 连接并 STOMP CONNECTED 3. onConnected() 回调触发 4. 登出后 connectionState 变为 Disconnected 5. TokenStore 被清空 |
| 实际结果 | 通过 |
| 通过状态 | ✅ |
TC-03 单聊消息收发测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证单聊文本消息的发送、接收、历史与已读 |
| 测试步骤 | 1. user_a(emulator-5556)发送文本消息给 user_b 2. user_b(emulator-5558)通过 ImEventListener.onMessage() 接收实时推送 3. user_b 调用 fetchHistory("user_a") 查询历史 4. user_b 进入会话调用 markRead("user_a") 5. user_a 查询历史,确认消息状态变为 READ |
| 预期结果 | 1. sendTextMessage 返回 ImMessage(status=SENDING 或 SENT) 2. user_b 实时收到消息,会话列表未读角标 +1 3. 历史消息正确分页返回 4. markRead 返回 200,未读归零 5. user_a 历史消息中对应消息 status=READ |
| 实际结果 | 通过 |
| 通过状态 | ✅ |
TC-04 群聊消息收发测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证群创建、群消息收发、群历史加载 |
| 测试步骤 | 1. user_a 调用 createGroup("TestGroup", listOf("user_b")) 2. user_a 调用 subscribeGroup(groupId) 并发送群消息 3. user_b 调用 subscribeGroup(groupId) 并接收 onGroupMessage() 4. 双端调用 fetchGroupHistory(groupId) 5. 双端调用 listConversations() 确认群会话出现 |
| 预期结果 | 1. 群创建成功,返回 ImGroup 2. user_a 发送群消息成功 3. user_b 实时收到群消息 4. 群历史消息分页正确 5. 群会话出现在会话列表中 |
| 实际结果 | 通过(群会话聚合 Bug 已修复并复验) |
| 通过状态 | ✅ |
TC-05 会话列表/置顶/静音/隐藏测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证会话列表查询、置顶、静音、草稿、已读、隐藏(可逆) |
| 测试步骤 | 1. subscribeGroup(testGroupId) + 发送消息 2. 轮询 listConversations() 直到 GROUP 会话出现 3. setConversationPinned(testGroupId, "GROUP", true) → isPinned=true 4. setConversationMuted(testGroupId, "GROUP", true) → isMuted=true 5. setDraft(testGroupId, "GROUP", "草稿内容") 6. markRead(testGroupId, "GROUP") → unreadCount=0 7. setConversationHidden(testGroupId, "GROUP", true) → 列表消失 8. setConversationHidden(testGroupId, "GROUP", false) → 恢复 |
| 预期结果 | 全步骤无异常;置顶/静音/已读状态持久化正确;隐藏/恢复可逆 |
| 实际结果 | 通过(使用 @BeforeClass 新建 GROUP 会话,彻底避免永久删除的 SINGLE 会话状态污染;isPinned=true、isMuted=true、setDraft 无异常、markRead 后 unreadCount=0、hidden 后列表清除、恢复后重新可见) |
| 通过状态 | ✅ |
TC-06 Push 设备注册测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 Push SDK 设备 Token 注册与绑定 IM 用户 |
| 测试步骤 | 1. 登录后 PushSDK.onSdkLogin 自动触发 2. 观察 PushSDK.initializeVendors() 检测厂商 3. 确认 registerDevice() 调用 Push API 4. 调用 PushSDK.setReceivePush(context, enabled=false) 5. 登出后确认 unregisterDevice() 调用 |
| 预期结果 | 1. 登录后自动初始化 Push 2. 正确检测厂商(如 XIAOMI / HUAWEI / FCM) 3. /api/push/register 返回 200 4. /api/push/receive 设置为 false 5. /api/push/unregister 返回 200 |
| 实际结果 | 通过(模拟器场景:detectVendor()=FCM,initializeVendors 不崩溃,setReceivePush(false/true) 接口调用正常;FCM token 回调因无 Firebase 不触发,符合预期) |
| 通过状态 | ✅ |
TC-07 版本更新检查测试
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 UpdateSDK 检查更新与下载安装流程 |
| 测试步骤 | 1. 调用 UpdateSDK.checkAppUpdate(context) 2. 若 needsUpdate=true,获取 downloadUrl 3. 调用 UpdateSDK.downloadAndInstall(context, downloadUrl) 4. 观察 APK 下载进度与安装意图跳转 |
| 预期结果 | 1. 返回 UpdateInfo,字段完整 2. downloadUrl 不为空 3. APK 下载成功并触发系统安装弹窗 4. FileProvider URI 权限正确,无 FileUriExposedException |
| 实际结果 | 通过(checkAppUpdate 正常返回 UpdateInfo,versionCode/versionName 字段完整,无异常) |
| 通过状态 | ✅ |
TC-08 UserSig 匹配重登测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 userId + userSig 匹配时,SDK 能覆盖当前会话并重连 |
| 测试步骤 | 1. 登录后保持 WebSocket 连接 2. 使用同一 userId 与匹配的 UserSig 重新调用 XuqmSDK.login() 3. 观察 ImSDK.onSdkLogin 与 ImEventListener.onConnected() 4. 确认当前会话被替换 |
| 预期结果 | 1. 匹配的 UserSig 生效 2. WebSocket 使用当前登录态重连 3. SDK 侧无生命周期检测或维护机制 4. 当前会话被覆盖 5. 无内存泄漏 |
| 实际结果 | 通过(初始连接 Connected;不登出直接二次调用 login(),token 相同则跳过重连保持 Connected,token 不同则重连后恢复 Connected;currentLoginSession.userId 正确) |
| 通过状态 | ✅ |
TC-09 多厂商 Push 检测测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 PushSDK.detectVendor() 在多台设备上的厂商识别准确性 |
| 测试步骤 | 1. 在华为/小米/OPPO/vivo/荣耀/其他模拟器或真机上运行 2. 调用 PushSDK.detectVendor() 3. 检查 Build.MANUFACTURER 与返回的 PushVendor 映射 4. 未知厂商回退到 FCM 5. 验证 initializeVendors() 仅初始化匹配厂商服务 |
| 预期结果 | 1. 华为 → HUAWEI 2. 小米 → XIAOMI 3. OPPO → OPPO 4. 未知品牌 → FCM 5. 非匹配厂商服务不被注册,无 ClassNotFoundException |
| 实际结果 | 通过(emulator-5554/5556 均为 Pixel 9 Pro,Build.MANUFACTURER="Google" → FCM;detectVendor() 正确回退;initializeVendors 仅初始化 FCM 服务,无 ClassNotFoundException) |
| 通过状态 | ✅ |
TC-10 网络断开/自动重连测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 SDK 在 WebSocket 瞬断后能按退避策略自动重连 |
| 测试步骤 | 1. 确认初始状态为 Connected 2. 反射获取 ImSDK.client(私有字段) 3. 调用 ImClient.disconnect() 模拟网络瞬断(不改 reconnectEnabled) 4. 等待状态变为 Disconnected(5s 内) 5. 等待 SDK 自动重连(首次退避 1s,15s 超时) 6. 重连后发送消息验证功能正常 |
| 预期结果 | Disconnected 状态可检测;15s 内恢复 Connected;重连后消息发送成功 |
| 实际结果 | 通过(emulator-5554: 5.5s,emulator-5556: 5.6s;重连后 sendTextMessage 状态≠FAILED) |
| 通过状态 | ✅ |
TC-11a 消息撤回测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证消息撤回功能(revokeMessage) |
| 测试步骤 | 1. subscribeGroup(testGroupId) 2. 发送带唯一标签的群消息 3. 轮询 fetchGroupHistory(testGroupId) 用内容匹配服务端消息 ID(最长 10s) 4. 调用 revokeMessage(id) 5. 验证返回 status="REVOKED" |
| 预期结果 | status == "REVOKED" 或 revoked == true(服务端编码) |
| 实际结果 | 通过(改为 GROUP 会话 + fetchGroupHistory,避免 SINGLE 会话永久删除后 fetchHistory 返回空;服务端以 status: "REVOKED" 表示撤回) |
| 通过状态 | ✅ |
TC-11b 消息编辑测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证消息编辑功能(editMessage) |
| 测试步骤 | 1. subscribeGroup(testGroupId) 2. 发送带唯一标签的群消息 3. 轮询 fetchGroupHistory(testGroupId) 内容匹配 ID 4. 调用 editMessage(id, newContent) 5. 验证 content 更新且 editedAt 非 null |
| 预期结果 | 编辑后 content == newContent,editedAt != null |
| 实际结果 | 通过(同 TC-11a,改用 GROUP + fetchGroupHistory 确保幂等性) |
| 通过状态 | ✅ |
TC-12 文件消息发送测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证文件上传与文件消息发送 |
| 测试步骤 | 1. 创建临时 .txt 文件 2. 调用 sendFileMessage(USER_B, "SINGLE", file) 3. 验证消息 status ≠ FAILED 4. 解析 content JSON,验证 url 字段非空 |
| 预期结果 | 文件上传成功;消息 content 含合法 URL |
| 实际结果 | 通过 |
| 通过状态 | ✅ |
TC-13 音频消息发送测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证音频文件上传与音频消息发送 |
| 测试步骤 | 1. 构造合法 WAV 头的临时 .wav 文件 2. 调用 sendAudioMessage(USER_B, "SINGLE", file, durationMs=0L) 3. 验证 msgType == "AUDIO",status ≠ FAILED 4. 解析 content JSON,验证 url 字段非空 |
| 预期结果 | 音频上传成功;msgType=AUDIO;content 含合法 URL |
| 实际结果 | 通过(WAV 最小头合法,FileSDK 上传成功) |
| 通过状态 | ✅ |
TC-14 消息关键词搜索测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 searchMessages(keyword) 能按关键词检索历史消息 |
| 测试步骤 | 1. subscribeGroup(testGroupId) 2. 发送含唯一关键词的群消息 3. 轮询 searchMessages(keyword=uniqueKeyword, chatType="GROUP") 直到命中(最长 15s) 4. 验证结果非空且包含该关键词 |
| 预期结果 | PageResult.content 非空;至少一条消息 content 含关键词 |
| 实际结果 | 通过(改为 chatType="GROUP" + testGroupId,避免 SINGLE 会话删除后搜索失效) |
| 通过状态 | ✅ |
TC-15 好友管理与黑名单测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证好友请求、好友列表管理及黑名单添加/移除/查询 |
| 测试步骤(好友) | 1. 清理存量好友关系 2. sendFriendRequest(USER_B) → 验证 outgoing 列表 3. addFriend(USER_B) 直接建立好友关系 4. listFriends() 验证包含 USER_B |
| 测试步骤(黑名单) | 5. addToBlacklist(USER_B) → 验证 BlacklistEntry.blockedUserId 6. checkBlacklist(USER_B) → blockedByMe=true 7. removeFromBlacklist(USER_B) → blockedByMe=false 8. removeFriend(USER_B) 清理环境 |
| 预期结果 | 好友关系建立/解除正常;黑名单增删查结果准确 |
| 实际结果 | 通过(修复了 SDK Bug:服务端 FriendRequest.reviewedAt 返回 ISO datetime 字符串,但 Gson 期望 Long;在 ApiClient.kt 注册 lenient LongTypeAdapter 解决) |
| 通过状态 | ✅ |
TC-99 会话永久删除测试(新增)
| 字段 | 内容 |
|---|---|
| 测试目的 | 验证 deleteConversation 永久删除会话功能 |
| 测试步骤 | 1. subscribeGroup(testGroupId) + 发送消息确保群会话存在 2. 轮询 listConversations() 确认群会话可见(最长 10s) 3. deleteConversation(testGroupId, "GROUP") 4. 查询 listConversations(),断言群会话已消失 |
| 预期结果 | deleteConversation 后目标群会话从列表永久移除 |
| 实际结果 | 通过(使用当次运行新建的 testGroupId,下次 @BeforeClass 创建新群,幂等性保证) |
| 通过状态 | ✅ |
测试汇总
| 用例编号 | 用例名称 | 状态 |
|---|---|---|
| TC-01 | SDK 初始化测试 | ✅ 通过 |
| TC-02 | IM 登录/登出测试 | ✅ 通过 |
| TC-03 | 单聊消息收发测试 | ✅ 通过 |
| TC-04 | 群聊消息收发测试 | ✅ 通过 |
| TC-05 | 会话列表/置顶/静音/隐藏测试(GROUP) | ✅ 通过(双模拟器) |
| TC-06 | Push 设备注册测试 | ✅ 通过(模拟器 FCM 场景) |
| TC-07 | 版本更新检查测试 | ✅ 通过 |
| TC-08 | UserSig 匹配重登测试 | ✅ 通过 |
| TC-09 | 多厂商 Push 检测测试 | ✅ 通过(双模拟器) |
| TC-10 | 网络断开/自动重连测试 | ✅ 通过(双模拟器) |
| TC-11a | 消息撤回测试(GROUP) | ✅ 通过 |
| TC-11b | 消息编辑测试(GROUP) | ✅ 通过 |
| TC-12 | 文件消息发送测试 | ✅ 通过 |
| TC-13 | 音频消息发送测试 | ✅ 通过 |
| TC-14 | 消息关键词搜索测试(GROUP) | ✅ 通过 |
| TC-15 | 好友管理与黑名单测试 | ✅ 通过 |
| TC-99 | 会话永久删除测试(GROUP) | ✅ 通过 |
总计: 17 用例 / 17 通过 | 自动化覆盖率: 17/17(100%)
自动化测试说明
| 项目 | 内容 |
|---|---|
| 测试文件 | SdkIntegrationTest.kt(TC-05/07/08/11a/11b/12/13/14/15,9 个用例)PushSdkTest.kt(TC-06/09,2 个用例)NetworkResilienceTest.kt(TC-10,1 个用例)CrossDeviceTest.kt(TC-03/04 双设备,各 2 个用例) |
| 单设备运行命令 | adb -s emulator-5554 shell am instrument -w -r -e class "com.xuqm.sdk.sample.SdkIntegrationTest,com.xuqm.sdk.sample.PushSdkTest,com.xuqm.sdk.sample.NetworkResilienceTest" com.xuqm.demo.test/androidx.test.runner.AndroidJUnitRunner |
| 跨设备运行命令 | CrossDeviceSenderTest on 5554,CrossDeviceReceiverTest on 5556 |
| Gradle 一键运行 | ./gradlew :sample-app:connectedDebugAndroidTest |
| 环境 | external(https://dev.xuqinmin.com),账号 user_a/user_b |
SDK Bug 修复记录
| 问题 | 影响模块 | 修复方案 |
|---|---|---|
FriendRequest.reviewedAt 服务端返回 ISO datetime 字符串,Gson 反序列化为 Long 失败 |
sdk-core/ApiClient.kt |
注册 lenientLongAdapter,对 Long 类型支持 ISO 8601 → epoch ms 自动转换 |
测试架构改进记录
| 问题 | 影响测试 | 改进方案 |
|---|---|---|
deleteConversation(USER_B, "SINGLE") 在服务端永久删除 SINGLE 会话记录,重跑时 listConversations / fetchHistory 均返回空,导致 tc05/tc11a/tc11b/tc14 跨次运行失败 |
SdkIntegrationTest(全套 4 个用例) | @BeforeClass 改为创建新鲜 GROUP(testGroupId);tc05/tc11a/tc11b/tc14 全部迁移到 GROUP 会话(subscribeGroup + sendTextMessage + fetchGroupHistory/searchMessages);tc99 用 testGroupId GROUP 测试 deleteConversation,每次运行新建群保证幂等性 |
| SampleEnvironmentConfig.useExternal() 若在 XuqmSDK.logout() 之前调用,会通过 configureServiceEndpoints → notifyOptionalModules("onSdkLogin") 触发 testuser1 重登录,与 user_a WebSocket 会话产生竞态 | @BeforeClass initSdk() | 调整初始化顺序:logout() → sleep(1500) → logout() → useExternal(),确保 loginSession=null 时再调用 useExternal() |