**背景**
在移动端开发中,单元测试是构建健壮移动应用的基石,然后在工程实践中,单元测试往往是资源最密集,最具挑战的环节之一。在做单元测试工作时遇到的难题往往如下:
- 难以捉摸的覆盖率:手动编写测试总会留下盲点,尤其是在复杂的 UI 逻辑、设备碎片化带来的边界条件和错误处理上;
- 参差不齐的质量:在缺乏严格统一范式的情况下,测试套件会沦为风格迥异的代码“补丁集”,可读性差,维护成本极高;
- 重复的样板代码:相当一部分测试工作是在编写重复的初始化、清理和模拟(Mock)逻辑,这极大地消耗了开发者的心力与时间。
为了解决这些问题,本文引入了 Cursor Rules 来辅助生成高质量的移动端单元测试。Cursor Rules 是 Cursor IDE 提供的一种代码上下文规则系统,可以为 AI 助手提供项目特定的知识和约束,从而生成更符合项目规范的代码。
与成熟的前后端生态不同,公开分享高质量、适用于移动端(iOS 和 Android)的 Cursor Rules 凤毛麟角,而专门用于生成高质量单元测试的成熟方案更是几乎不存在。移动端开发的 AI 辅助测试的生态,目前仍处于 “拓荒” 阶段,这个“最后一公里”的缺失,导致开箱即用的 AI 工具所生成的代码往往过于泛化甚至错误,无法满足专业级移动开发的需求。
本文旨在呈现一套完整的、从零到一创建和优化移动端单元测试 Cursor Rules 的实践方案,并且本文以开发移动端性能体验监控 SDK 为实战案例,详细展示 iOS 和 Android 两端如何通过 Cursor Rule 辅助生成健壮的单元测试。
**Cursor Rules 使用方式说明**
官方文档链接:https://docs.cursor.com/zh/context/rules
**Cursor rule 种类**
- 项目规则:存储在 ./cursor/rules 中,受版本控制并作用于您的代码库;
- 用户规则:全局应用于您的 Cursor 环境。在设置中定义并始终生效;
- .cursorrules (旧版):仍然支持,但是已经弃用。最好使用项目规则替代。

**Cursor rules 生效规则**
Cursor Rules 支持四种不同的生效规则,每种规则适用于不同的场景:
- Always(始终生效)
- 使用场景:项目核心架构和设计原则 / 全局编码规范和命名约定 / 项目结构和模块依赖关系 / 通用的最佳实践
- 示例:比如README.mdc、module-architecture.mdc、naming-conventions.mdc 等规则文件使用此模式,确保 AI 助手始终了解项目的基本结构和规范。
- Auto Attached(自动附加)
- 使用场景:特定文件类型的编码规范 / 语言特定的最佳实践 / 文件类型相关的工具使用
- 示例:coding-style.mdc 规则针对源代码文件(*.h, *.m, *.mm, *.swift)自动生效,确保代码风格一致性。
- Agent Requested(对 AI 可用,由其自行决定是否包含。必须提供描述)
- 使用场景:特定功能的详细指导 / 复杂流程的步骤说明 / 专业领域的知识规范
- 示例:单元测试unit-testing.mdc 规则使用此模式,当涉及单元测试相关任务时,AI 助手会主动获取这些规则。
- Manual(手动应用)
- 使用场景:临时性的特殊需求 / 调试和故障排除指南 / 高级配置和优化建议
- 示例:build-and-debug.mdc 和 contribution-guide.mdc 需要手动引用,用于解决特定问题或进行代码贡献。
**官方最佳实践与约束**
- 规则保持 500 行以内
- 大型规则需要拆分多个可组合的规则
- 提供具体的示例或引用文件
- 避免模糊指导,规则需要清晰
- 在聊天时需要重复提示的规则优先写入规则中
**使用 Cursor 命令生成 Cursor Rules**
可以使用 cursor 的命令 /Generate Cursor Rules 命令生成 cursor rule,再进行微调。可以直接将 /Generate Cursor Rules 输入给 cursor,它会自动生成包含上下文等的工程相关 rule。也可以在命令后指定要生成的命令格式。

**实战与微调**
**参考内容**
可以参考的 Cursor Rules 网站:
https://cursor.directory/rules/testing
https://www.cursorrulescn.cn/19/
https://dotcursorrules.com/rules
https://github.com/PatrickJS/awesome-cursorrules
筛选所有参考网站中的 Android 和 iOS 与测试相关的 Cursor rule,如下内容:
- Android:https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/android-jetpack-compose-cursorrules-prompt-file/android-jetpack-compose---testing-guidelines.mdc
- Android:https://cursor.directory/android
- iOS:https://cursor.directory/swiftui-swift-simple-developer-cursor-rules
从上述参考中,对移动端单测可复用的要点如下:
- 结构与命名:
- 用 AAA(Arrange-Act-Assert)或 Given-When-Then 写用例;每个测试聚焦一个行为。
- 测试变量命名清晰,建议 inputX/mockX/actualX/expectedX 前缀。
- 范围与分层:
- 明确单测与 UI/E2E 的边界;本文聚焦单测,UI/E2E 仅做关键用户流验收。
- 依赖隔离:
- 使用 Fake/Stub/Mock 隔离网络、存储、时钟、全局单例等外部依赖。
- 第三方库直用边界:仅限纯函数、无状态、无 I/O 的库;否则一律替身。
- 框架与工具:
- Android:JUnit/Mockito/Robolectric,Compose UI Test 仅用于关键流程;协程用测试调度器。
- iOS:XCTest 做单测,XCUITest 做 UI 测试;常见用户流、异常与性能点需覆盖。
- 质量与鲁棒性:
- 覆盖正常、边界与错误路径;控制时间/线程/随机性以保证确定性;避免脆弱(实现细节耦合)的断言。
**如何设计高效 Prompt**
**iOS 端 Prompt**
在工程完成 indexing 后,在对话框输入 /Generate Cursor Rules,Cursor 会自动根据当前工程,生成多个 rules 文件,并自动配置其生效规则,我们可以首先阅览一下各个文件的内容,并根据实际情况做出微调与改动。

此时 cursor 对话中,上下文中已经包含了当前工程的架构以及规范等基础知识,此时我们再使用命令针对性地生成一个用于单元测试的 rule,命令输入如下:
/Generate Cursor Rules 请为我们的 iOS SDK 创建一套单元测试生成和优化规则。整个流程的核心是先检查测试文件是否存在:如果不存在,就根据一个包含状态重置逻辑 (setUp) 和核心场景测试(单例、主要功能、边界,错误处理)的模板创建新文件,然后必须将其添加到项目 Target 并立即运行验证;如果文件已存在,则读取并分析它,补充缺失的测试并优化断言,然后直接验证。此规则有两条最重要的强制约束:第一,所有代码注释和断言消息必须是英文,严禁任何中文;第二,所有新文件都必须遵循 Tests/模块名Tests/ 的路径结构,最终代码必须编译通过且测试无误。规则保持 500 行以内。
这个 Prompt 需要提到几个关键点:
- 由于当前工程已经有单元测试,所以需要分场景分别去进行单元测试生成或者优化,首先检查测试文件是否存在,不存在的话创建新文件,如果存在,则读取并分析,补充缺失的测试并优化。
- 在生成单元测试之后,一定要 ai 主动执行命令运行并进行验证。验证的命令在不通端有不同的验证方式,Cursor 自动生成的 rule 中的验证方式基本上都不会一次运行通过,所以需要在生成之后,再进行手动微调。这里以 iOS 端新增文件为例。
- 首先以自动化的方式,将一个指定的新源文件(.swift 测试文件)添加到 Xcode 项目的特定目标(Target)中,并确保该文件在 Xcode 的项目导航器中出现与其文件系统路径相匹配的正确组(Group)结构下。简单来说,这一步代替了开发者手动将文件拖拽到 Xcode 中并选择 Target 的过程。
- 接下来执行运行单测的脚本,如果需要做到仅对本次修改的单测文件进行验证,需要在测试脚本中新增-only-testing命令。
- 如果项目中有未来开源的需求,可以考虑规定单元测试的注释为英文,避免中文注释。
- 最后一条也是最重要的规则,就是你期待他帮你生成什么样的单元测试,遵循什么样的测试架构。比如在本工程中,我们指定了测试需要覆盖的四大核心场景:
- 单例模式:SDK 中单例非常普遍,保证其唯一性是防止状态混乱和多线程问题的关键。
- 主要功能 :确保核心业务逻辑按预期工作,这是测试的基本盘。
- 边界条件:nil 输入、空字符串等是导致程序崩溃或异常行为的常见原因。强制测试这些边界情况,能显著增强 SDK 的健壮性 。
- 错误处理:考验代码在非理想情况下的反应,确保程序能优雅地处理错误,而不是直接崩溃。
**Android 端 Prompt**
在生成 Android cursor rule 时,它的核心思想与 iOS 版本类似,但技术栈和具体约束不同,Android 强调隔离业务逻辑与 Android 框架依赖,并且主要是围绕 JUnit, Mockito, 和 Robolectric 的最佳实践。
下面是一份生成 Android 单元测试的 Cursor rule 的 Prompt。
/Generate Cursor Rules 请为我们的 Android SDK 创建一个全面的单元测试生成和优化规则。规则的核心是严格分离业务逻辑与 Android 框架的依赖。所有测试都应围绕 JUnit, Mockito, 和 Robolectric 展开。
- 标准类模板要求:
生成的所有新测试类都必须基于一个标准模板。该模板必须包含:
使用 @RunWith(RobolectricTestRunner.class)。
一个 @Before 方法,用于通过 MockitoAnnotations.openMocks(this) 初始化 Mock 对象,并对所有必要的静态类(如 Clock, Utils)进行 mockStatic 设置。
一个 @After 方法,用于必须按创建逆序清理所有在 @Before 中创建的 MockedStatic 对象,以防止测试间状态污染。
为 Mocks、静态 Mocks 和被测对象(Instance Under Test)提供清晰的字段声明区域。 - 核心命名规范:
测试类命名:必须遵循 {ClassName}Test.java 格式。
测试方法命名:必须遵循 test{Method}{Scenario}{ExpectedResult} 格式,清晰地表达测试意图。 - 关键原则与严格禁令:
规则必须严格禁止任何试图模拟或依赖特定 Android 系统环境的行为。核心原则是:测试我们自己的代码逻辑,而不是 Android 框架的行为。
严禁模拟 Android 系统版本:绝对禁止使用 ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", ...) 这样的代码。应通过参数化测试来验证逻辑,而不是模拟系统版本。
严禁反射修改系统字段:禁止通过反射修改任何 Build 类的静态字段。
禁止深度系统 API 模拟:避免验证依赖系统常量(如 Context.RECEIVER_EXPORTED)的具体 API 调用。测试应关注业务方法能否被正常调用,而不是验证系统 API 的内部行为。
禁止测试第三方库和框架 API:明确排除对 OkHttp、Gson 或 Context.getSystemService() 等第三方或系统框架内部逻辑的测试。 - 测试覆盖率策略:
必须测试:构造函数(正常、null、无效参数)、所有公共方法的正常流程与边界条件、状态变更逻辑(如 install/uninstall)以及异常处理。
禁止测试:无逻辑的简单 getter/setter 方法。 - 测试执行:
指明标准的测试执行命令是 ./gradlew :模块名:testDebugUnitTest。 - 工作流程:
规则应指导 AI 遵循一个清晰的工作流:首先分析源代码和现有测试,然后设计 Mock 策略,接着基于模板和规则生成或优化代码,最后必须运行测试进行验证。
此规则有两条最重要的强制约束:第一,所有代码注释和断言消息必须是英文,严禁任何中文;第二,最终代码必须编译通过且测试无误。规则保持 500 行以内,请将以上所有要求整合到一个完整的 cursor.rule 文件中。
具体在不同的场景下需要进行适配修改,思想可以归纳为以下几点:
- 关注业务逻辑,而非框架实现。单元测试的职责是验证我们自己写的代码逻辑是否正确,而不是去测试系统 API、第三方库或 Java 基础库的行为。
- **彻底的依赖隔离。**为了确保测试的稳定性和确定性,规范要求将被测对象与其所有外部依赖完全隔离。
- 标准化与结构化。规范通过提供模板和严格的命名约定,确保了所有单元测试在结构和风格上的一致性,极大地降低了阅读和维护成本。
- 全面的测试覆盖策略。规范明确定义了哪些代码必须测试、哪些可以选测、哪些禁止测试,为开发者提供了清晰的指引,避免在低价值的测试上浪费时间。
- 健壮性与可维护性。规范通过一系列最佳实践来保证测试代码本身的质量。
**小结**
在设计高效的移动端单元测试的 Prompt 时,思路总结为以下几点:
- 明确角色与目标:“请为我们的 iOS SDK 创建一套单元测试生成和优化规则”
- 设定清晰的流程与场景:“先检查文件是否存在...如果不存在...如果已存在...”
- 施加刚性约束:“所有代码注释和断言消息必须是英文,严禁任何中文”
- 指定验证闭环:“必须将其添加到项目 Target 并立即运行验证”
**Cursor Rules 的内容及其运用效果**
**iOS Cursor Rules**
globs: \*Tests.swift,\*Test.swift,\*Tests.m,\*Test.m description: 阿里云RUM SDK单元测试生成和验证规范
🧪 单元测试生成和验证指南
🔍 开始前必须检查测试文件是否存在
# 检查对应的测试文件(以ALRDeviceHelper为例)
find Tests -name "\*DeviceHelper\*Tests.swift" -type f
# 有输出 -> 场景2:优化现有测试(先读取分析再优化)
# 无输出 -> 场景1:创建新测试文件(按模板创建)
📝 测试类模板(场景1:新建测试)
import XCTest
@testable import AlibabaCloudCore
final class ALRYourClassTests: XCTestCase {
override func setUp() {
super.setUp()
ALRYourClass.shared().reset() // Reset singleton state
}
// Required test: singleton pattern (if applicable)
func test\_singletonInstance() {
let instance1 = ALRYourClass.shared()
let instance2 = ALRYourClass.shared()
XCTAssertTrue(instance1 === instance2, "Should be same instance")
}
// Required test: main functionality
func test\_mainFunctionality() {
// Arrange
let instance = ALRYourClass.shared()
// Act
instance.performAction("test")
// Assert
XCTAssertEqual(instance.result, "test", "Should set value correctly")
}
// Required test: edge cases
func test\_edgeCases\_nilInput() {
XCTAssertNoThrow {
ALRYourClass.shared().handleNilInput(nil)
}
}
}
🔧 优化现有测试策略(场景2)
- **读取现有测试文件**
- **分析缺失**:哪些方法没有测试,哪些边界条件未覆盖
- **改进质量**:添加断言描述信息,修复setUp/tearDown
- **补充测试**:添加缺失的测试场景
⚡ 即时验证流程
场景1:新建测试文件后的验证步骤
- **添加测试文件到测试目标**
# 确保脚本有执行权限
chmod +x Scripts/add\_file\_to\_target.sh
# 添加新测试文件到测试目标(使用相对于项目根目录的完整路径)
# 脚本会自动创建正确的组结构,确保文件在Xcode中显示在正确的目录层级
Scripts/add\_file\_to\_target.sh Tests/AlibabaCloudCoreTests/ALRYourClassTests.swift
- **运行测试验证**
cd Scripts
./run-unittest.sh ALRYourClassTests
场景2:优化现有测试文件的验证步骤
# 直接运行测试验证(无需添加到目标)
cd Scripts
./run-unittest.sh ALRYourClassTests
必须通过的检查
- ✅ 编译无错误
- ✅ 所有测试方法通过
- ✅ 无运行时异常
🎯 必须测试的核心场景
- **单例模式**:
test_singletonInstance()(如适用) - **主要功能**:
test_mainFunctionality() - **边界条件**:
test_edgeCases_nilInput(),test_edgeCases_emptyInput() - **错误处理**:
test_errorHandling_invalidInput()
📝 代码注释规范
**🚨 重要要求:所有测试代码中的注释必须使用英文,严禁使用中文字符**
✅ 正确示例
// Test singleton pattern functionality
func test\_singletonInstance() {
// Arrange: Get two instances
let instance1 = ALRYourClass.shared()
let instance2 = ALRYourClass.shared()
// Assert: Should be the same instance
XCTAssertTrue(instance1 === instance2, "Should be same instance")
}
// Test edge case with nil input
func test\_edgeCases\_nilInput() {
// Should not throw when handling nil input
XCTAssertNoThrow {
ALRYourClass.shared().handleNilInput(nil)
}
}
❌ 错误示例
// 测试单例模式功能 - ❌ 禁止使用中文
func test\_singletonInstance() {
// 获取两个实例 - ❌ 禁止使用中文
let instance1 = ALRYourClass.shared()
let instance2 = ALRYourClass.shared()
// 应该是同一个实例 - ❌ 禁止使用中文
XCTAssertTrue(instance1 === instance2, "Should be same instance")
}
英文注释指南
- 使用简洁明了的英文描述测试目的
- 遵循 Arrange-Act-Assert 模式的注释风格
- 断言消息使用英文描述期望结果
- 避免使用任何中文字符、标点符号或表情符号
📁 文件路径和组结构规范
正确的测试文件路径格式
- **完整路径格式**:
Tests/{ModuleName}Tests/{TestFileName}.swift - **示例路径**:
Tests/AlibabaCloudCoreTests/ALRDeviceHelperTests.swiftTests/AlibabaCloudCaptureTests/ALRCaptureServiceManagerTests.swiftTests/AlibabaCloudOpenTelemetryTests/AlibabaCloudOTelTests.swift
脚本修复说明
- ✅ **已修复问题**:
Scripts/add_file_to_target.sh脚本现在会正确创建Xcode组结构 - ✅ **自动组织**:新测试文件会自动添加到相应的测试模块目录下,而不是根目录
- ✅ **路径解析**:脚本会解析完整路径并创建对应的组层级结构
使用示例
# 为Core模块创建测试文件
Scripts/add\_file\_to\_target.sh Tests/AlibabaCloudCoreTests/ALRSamplerTests.swift
# 为Capture模块创建测试文件
Scripts/add\_file\_to\_target.sh Tests/AlibabaCloudCaptureTests/ALRCaptureNewServiceTests.swift
# 为OpenTelemetry模块创建测试文件
Scripts/add\_file\_to\_target.sh Tests/AlibabaCloudOpenTelemetryTests/NewExporterTests.swift
🚨 常见问题快速修复
编译错误
// Ensure correct imports
import XCTest
@testable import AlibabaCloudCore
断言失败
// Add descriptive messages
XCTAssertEqual(actual, expected, "Specific error description")
状态污染
override func setUp() {
super.setUp()
ALRYourClass.shared().reset() // Reset singleton state
}
🤖 AI助手使用指南
标准流程
- **先检查**:
find Tests -name "*ClassName*Tests.swift" -type f - **选择场景**:
- **有输出 → 场景2:优化现有测试**
- 读取现有测试文件,分析后优化
- 直接运行验证:
cd Scripts && ./run-unittest.sh ALRClassTests
- **无输出 → 场景1:创建新测试文件**
- 按模板创建新测试文件
- **必须先添加到测试目标**:
Scripts/add_file_to_target.sh Tests/AlibabaCloudCoreTests/ALRClassTests.swift
- ⚠️ **路径规则**:必须使用完整的相对路径(从项目根目录开始)
- ✅ **脚本已修复**:会自动创建正确的Xcode组结构,不会再添加到根目录
- 运行验证:
cd Scripts && ./run-unittest.sh ALRClassTests
最佳实践
- 始终先检查测试文件是否存在
- **新建测试文件后必须先添加到测试目标再运行测试**
- 读取现有测试文件后再分析优化
- 每次生成/优化后立即运行验证
⚠️ 重要提醒
- 场景1(新建测试):必须执行
Scripts/add_file_to_target.sh脚本 - 场景2(优化现有测试):可以直接运行测试验证
iOS Cursor Rule 可以点击此处下载(简化展示)。整体 Rule 的逻辑思路:

**Android Cursor Rule**
description: "阿里云RUM Android SDK单元测试开发和优化综合指南"
阿里云RUM Android SDK 单元测试综合指南
适用于新建测试文件和改进现有测试。
标准测试类模板
@RunWith(RobolectricTestRunner.class)
public class {ClassName}Test {
// 静态Mock声明(按字母顺序)
private MockedStatic<Clock> clockMockedStatic;
private MockedStatic<Logger> loggerMockedStatic;
private MockedStatic<Utils> utilsMockedStatic;
// 实例Mock声明
@Mock private Context mockContext;
@Mock private Options mockOptions;
// 被测试对象
private {ClassName} instanceUnderTest;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
clockMockedStatic = mockStatic(Clock.class);
loggerMockedStatic = mockStatic(Logger.class);
utilsMockedStatic = mockStatic(Utils.class);
// 设置通用Mock行为
utilsMockedStatic.when(Utils::randomUUID).thenReturn("test-uuid");
clockMockedStatic.when(Clock::getCurrentTimeMillis).thenReturn(1000L);
instanceUnderTest = new {ClassName}(mockOptions);
}
@After
public void tearDown() {
// 清理静态Mock(按创建的逆序)
if (utilsMockedStatic != null) utilsMockedStatic.close();
if (loggerMockedStatic != null) loggerMockedStatic.close();
if (clockMockedStatic != null) clockMockedStatic.close();
}
@Test
public void test{Method}\_{Scenario}\_{ExpectedResult}() {
// Arrange
String inputValue = "test\_input";
// Act
String result = instanceUnderTest.method(inputValue);
// Assert
assertEquals("Should return expected result", "expected", result);
}
}
核心规范
命名规范
- 测试类:
{被测类名}Test.java - 测试方法:
test{方法名}_{测试场景}_{期望结果}
必需依赖
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.14.2'
testImplementation 'org.robolectric:robolectric:4.7.3'
Mock使用
// 静态Mock - 字段声明方式(类级别)
utilsMockedStatic.when(Utils::randomUUID).thenReturn("test-uuid");
// 静态Mock - try-with-resources方式(方法级别)
try (MockedStatic<Utils> utilsMock = mockStatic(Utils.class)) {
utilsMock.when(Utils::randomUUID).thenReturn("test-uuid");
// 测试代码
}
// 实例Mock
when(mockContext.getPackageName()).thenReturn("test.package");
when(mockService.riskyMethod()).thenThrow(new RuntimeException("Test"));
测试执行
# ✅ 正确命令
./gradlew :模块名:testDebugUnitTest
./gradlew testDebugUnitTest
./gradlew jacocoTestReport
# ❌ 避免使用
./gradlew :模块名:test --tests "TestClass"
🚫 严格禁止的测试类型
❌ Android系统版本测试
// ❌ 禁止:模拟Android版本
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK\_INT", Build.VERSION\_CODES.TIRAMISU);
// ❌ 禁止:版本相关API测试
@Test
public void testMethod\_Android13AndAbove\_ShouldUseNewAPI() {
// 这类测试在单元测试中几乎总是失败
}
// ✅ 正确:参数化测试
@Test
public void testMethod\_WithExportedFlag\_ShouldRegisterCorrectly() {
// 直接测试方法的不同参数组合
}
❌ 反射修改系统字段
// ❌ 禁止:修改Build类静态字段
ReflectionHelpers.setStaticField(Build.class, "SUPPORTED\_ABIS", mockAbis);
// ✅ 正确:测试方法直接输入输出
@Test
public void testGetArchitectures\_ValidInput\_ShouldReturnExpected() {
// 直接测试方法逻辑,不依赖系统字段
}
❌ 深度系统API模拟
// ❌ 禁止:模拟系统常量
when(mockContext.registerReceiver(any(), any(), eq(Context.RECEIVER\_EXPORTED)))
// ✅ 正确:测试业务逻辑
@Test
public void testRegisterReceiver\_ValidParameters\_ShouldNotThrow() {
// 测试方法调用正常,不验证系统API具体行为
}
RUM SDK特定测试模式
Capture服务测试
@Test
public void testOnInstall\_ValidConfig\_ShouldInitializeCorrectly() {
// Arrange
CaptureConfig config = mock(CaptureConfig.class);
when(config.isEnabled()).thenReturn(true);
// Act
service.onInstall(config);
// Assert - 验证关键状态变化,不验证日志
assertTrue("Service should be installed", service.isInstalled());
}
Manager类测试
@Test
public void testGetInstance\_FirstCall\_ShouldCreateSingleton() {
// Act
Manager instance1 = Manager.getInstance();
Manager instance2 = Manager.getInstance();
// Assert
assertNotNull("Instance should not be null", instance1);
assertSame("Should return same instance", instance1, instance2);
}
Utils类测试
@Test
public void testUtilMethod\_EmptyString\_ShouldReturnDefault() {
// Arrange
String emptyInput = "";
String expectedDefault = "default\_value";
// Act
String result = UtilsClass.utilMethod(emptyInput);
// Assert
assertEquals("Should return default for empty input", expectedDefault, result);
}
测试覆盖率策略
✅ 必须测试
- **构造函数**:正常参数、null参数、无效参数
- **公共方法**:正常流程、边界条件、异常情况
- **状态变更**:安装/卸载、启用/禁用、初始化/销毁
- **异常处理**:系统异常、业务异常、网络异常
🔶 可选测试
- **日志验证**:仅在日志是核心功能时验证
- **私有方法**:通过公共方法间接测试
- **性能测试**:仅在性能关键路径上添加
❌ 禁止测试
- **第三方库行为**:如OkHttp、Gson的内部逻辑
- **Android框架API**:如Context.getSystemService()
- **简单getter/setter**:无业务逻辑的属性访问
- **Android系统版本相关**:模拟Build.VERSION.SDK_INT等
- **反射修改系统字段**:ReflectionHelpers.setStaticField等
- **深度系统API行为**:Context.RECEIVER_EXPORTED等
常见问题修复
Mock初始化
// ❌ 已废弃
MockitoAnnotations.initMocks(this);
// ✅ 推荐使用
MockitoAnnotations.openMocks(this);
tearDown清理
@After
public void tearDown() {
// ✅ 正确实现
if (utilsMockedStatic != null) utilsMockedStatic.close();
if (loggerMockedStatic != null) loggerMockedStatic.close();
}
测试方法命名
// ❌ 不规范
public void testOnInstall\_shouldCallStartPerformanceDataAndLog()
// ✅ 规范命名
public void testOnInstall\_NormalFlow\_ShouldCallStartAndLog()
特殊情况处理
处理Android系统API限制
@Test
public void testMethodWithSystemAPI\_InTestEnvironment\_ShouldHandleGracefully() {
try {
String result = instanceUnderTest.methodWithSystemAPI();
assertNotNull("Should return result if available", result);
} catch (Exception e) {
assertTrue("Should handle system limitations gracefully",
e instanceof SecurityException || e instanceof UnsupportedOperationException);
}
}
处理静态依赖
@Test
public void testMethodWithStaticDependency\_ValidInput\_ShouldReturnResult() {
try (MockedStatic<Utils> utilsMock = mockStatic(Utils.class)) {
utilsMock.when(() -> Utils.staticMethod(anyString())).thenReturn("mocked\_result");
String result = instanceUnderTest.methodUsingStaticUtils("input");
assertEquals("Should return processed result", "expected", result);
}
}
处理异步操作
@Test
public void testAsyncMethod\_ValidCallback\_ShouldInvokeCallback() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<String> result = new AtomicReference<>();
Callback<String> callback = new Callback<String>() {
@Override
public void onSuccess(String data) {
result.set(data);
latch.countDown();
}
@Override
public void onError(Exception e) {
latch.countDown();
}
};
instanceUnderTest.asyncMethod("input", callback);
assertTrue("Callback should be invoked within timeout",
latch.await(5, TimeUnit.SECONDS));
assertNotNull("Result should be set", result.get());
}
测试生成工作流
第一阶段:分析代码
- 读取源代码文件 - 分析类职责、识别公共方法、确定依赖关系
- 读取现有测试文件 - 评估覆盖率、识别质量问题、确定补充场景
- 分析模块依赖 - 检查build.gradle、确定Mock组件、识别Android依赖
第二阶段:设计策略
- 识别需要测试的公共方法
- 确定Mock策略和依赖注入点
- 设计测试数据和边界条件
第三阶段:生成代码
- 使用标准模板生成测试框架
- 为每个公共方法生成对应测试
- 添加异常场景和边界条件测试
- 优化现有测试的结构和命名
第四阶段:验证完善
- 运行测试确保通过
- 检查测试覆盖率
- 根据运行结果调整Mock策略
- 补充遗漏的测试场景
禁止事项
- ❌ 测试中使用真实网络请求、文件I/O
- ❌ 依赖系统时间或随机数据
- ❌ 忘记清理静态Mock对象
- ❌ 模拟Android系统版本或Build字段
- ❌ 使用ReflectionHelpers修改系统常量
- ❌ 测试依赖具体Android版本的API行为
测试质量检查清单
- 测试类命名遵循
{ClassName}Test模式 - 测试方法命名遵循
test{Method}_{Scenario}_{Expected}模式 - 使用
MockitoAnnotations.openMocks(this)初始化 - 所有静态Mock在
tearDown中正确清理 - 每个测试只验证一个功能点
- 使用有意义的断言消息
- 处理Android系统API的测试环境限制
- 运行测试验证通过
🎯 关键原则
- ✅ **测试业务逻辑,不测试系统行为**
- ✅ **验证方法输入输出,不验证Android框架**
- ✅ **使用参数化测试,不使用版本模拟**
- ✅ **确保测试稳定运行,不依赖系统环境**
**核心提醒**: 生成测试后必须立即运行验证,确保所有测试用例都能成功执行并通过。
Android Cursor Rule 可以点击此处下载(简化展示)。整体 Rule 的逻辑思路:

**使用效果**
在安卓端,我们以一个典型的数据处理模块为例,来展示 Cursor Rules 的效果。该模块的核心职责是接收用户的点击等行为,为其附加上下文信息(如时间戳、当前页面),然后打包发送给后端服务。这类模块的测试难点在于,它常常需要调用系统工具来获取实时信息,这使得单元测试变得不可靠。
优化前存在的问题:
- 缺乏隔离,测试不稳定:测试代码与真实的系统环境(如当前时间)耦合,导致每次运行结果都可能不同,失去了单元测试的确定性。
- 验证流于表面:只检查了某个函数“是否被调用”,未验证“传递的数据是否正确”。
- 场景覆盖不足:缺少对空值、异常输入等场景的测试。
Cursor Rule 的优化点:
- 实现彻底的依赖隔离:精准控制所有外部依赖,将不稳定的测试环境改造为完全受控的“沙箱”,确保了测试结果的确定性与稳定性。
- 验证核心数据内容:测试的重点从“方法是否被调用”升级为“传递的数据是否正确”。通过深度检查最终产出的数据包内容,确保了每一个关键字段的准确性,保障了数据上报的质量。
- 构筑全面场景覆盖:新增测试用例,覆盖了 null 值、空字符串、依赖项异常等被忽略的边界和异常场景。这确保了代码在真实世界的复杂情况下依然能够稳定运行,增强了其健壮性。
优化前后对比效果参考表格数据:
| 对比维度 | 指标 | 优化前 | 优化后 | 说明 |
|---|---|---|---|---|
| 基础指标 | 测试用例数量 | 8个 | 14个 | 新增6个关键场景 |
| 测试通过率 | 75% (6/8通过) | 100% (14/14通过) | 修复2个失败用例 | |
| 代码行数 | 295行 | 350行 | 增加功能性测试 | |
| 验证方法数量 | 3个冗余方法 | 1个通用方法 | 提高代码复用 | |
| Mock 策略 | 静态方法Mock | 依赖真实OpenTelemetry | MockedStatic | 完全隔离外部依赖 |
| Mock生命周期 | 部分管理 | 完整setUp/tearDown | 防止内存泄漏 | |
| 依赖隔离度 | 低(需外部初始化) | 高(完全Mock) | 测试稳定性保证 | |
| 测试覆盖 | 构造函数测试 | 1个 | 2个 | 增加继承关系验证 |
| 正常流程测试 | 1个 | 1个 | 优化Mock策略 | |
| 边界条件测试 | 4个 | 4个 | 优化验证逻辑 | |
| 异常处理测试 | 2个(失败) | 2个(通过) | 正确处理异常传播 | |
| 数据完整性测试 | 0个 | 3个 | 验证事件数据字段 | |
| 多次调用测试 | 0个 | 2个 | 验证重复调用行为 | |
| 代码质量 | 命名规范遵循 | 部分遵循 | 100%遵循 | test{Method}\_{Scenario}\_{Expected} |
| 结构规范性 | 基础AAA模式 | 完整AAA+注释 | Arrange-Act-Assert | |
| Mock管理规范 | 基础 | 完整生命周期 | 按顺序初始化和清理 | |
| 异常处理方式 | 简单断言 | try-catch+验证 | 符合测试环境特点 |
**Cursor Rules 的可持续维护策略**
为了确保 Cursor Rules 在项目中长期有效并能适应项目的演进,就必须有一套可持续的维护策略,核心思想是,要像管理源代码一样管理 Rules,确保 Rules 的质量、一致性和时效性。可以通过实施以下几点来进行管理:
- 版本化管理:所有 Cursor Rules 存储在代码仓库的 ./cursor/rules 目录下,和源代码一样进行版本管理与代码评审;
- CI/CD 驱动的自动化质量保障:可以在 CI/CD 门禁检查中,添加关于 Cursor Rules 的检查,例如包括
- 行数限制: 编写一个脚本,自动检查 ./cursor/rules 目录下的所有 .mdc 文件,确保其内容不超过官方建议的 500 行;
- 废弃 API 扫描:维护一个废弃 API 或不推荐写法的清单(例如,Android 规则中的 MockitoAnnotations.initMocks(this))。CI 脚本会扫描规则文件,若发现推荐使用废弃写法的示例,需要报错强制更新;
- 描述完整性检查:对于生效规则为 Agent Requested 的 Rule,强制要求其 description 字段不能为空且有意义,确保 AI 可以准确理解其用途;
- 模版语法校验:在规则中的代码模版是 AI 生成代码的基础,需要保证其正确性,可以编写一个 CI 脚本,提取规则文件中特定语言的代码块(如 Swift,Java,Kotlin),并调用相应的静态分析工具对其进行语法检查。例如,提取 Switt 模版代码并尝试用 swift -syntax-only 进行编译。这能有效防止因模版中的拼写错误而导致 AI 生成大量无法编译的代码。
- 上述所有检查都应整合到项目的 CI 配置文件中(如 .github/workflows/main.yml 或 .gitlab-ci.yml)。可以创建一个名为 lint-cursor-rules 的独立任务,该任务与单元测试、编译等任务并行执行。在代码仓库的保护分支策略中,将 lint-cursor-rules 设置为必须通过的检查项。这样一来,任何不符合规范的规则变更都会被 CI 系统自动拦截,无法合入主干,从而形成一个健壮的、自动化的维护闭环。
**总结与展望**
回顾本文的探索之旅,本文从移动端单元测试的普遍痛点出发,面对 AI 辅助工具在移动生态的空白,最终通过定制化的 Cursor Rules,找到了一条提升单测质量与效率的可行路径。本文展示了如何通过具体的指令、模版和约束,将 AI 的能力聚焦于我们的实际需求,让它生成的代码不再是“看起来正确”,而是真正可用且优质,无论是 iOS 端的自动化验证流程,还是 Android 端对框架依赖的严格隔离,这些细节共同构成了一套能显著改善日常开发体验的解决方案。
进一步地,针对本文的思路举一反三,你可以将这种模式应用到任何具有重复性和规范性的编码任务中,例如:快速创建符合团队代码风格的 UI 控件;为新的业务模块生成基础的数据模型和 service 层代码;批量为老代码添加规范化的文档注释。核心是:凡是你能总结出规律和模板的工作,都可以尝试用 Cursor Rules 来自动化。
本文通过 Cursor Rules 展示了如何在开发阶段构筑高质量的单元测试,从源头保证应用的健壮性。然而,应用的质量保障是一个完整的闭环,线上环境的真实用户体验才是最终的试金石。当代码发布上线后,如何精准捕捉性能瓶颈、定位偶现崩溃、洞察用户真实交互体验,就成了新的挑战。阿里云用户体验监控是一款专为移动应用打造的性能与体验监控利器,它提供了应用崩溃、卡顿、网络错误、页面加载等关键性能指标的实时监控。可以参考接入文档体验使用。相关问题可以加入“RUM 用户体验监控支持群”(钉钉群号: 67370002064)进行咨询。