mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
feature: Weex APM
This commit is contained in:
@@ -1177,6 +1177,45 @@ SPEC_END
|
||||
|
||||
## 五、UI 测试
|
||||
|
||||
### 概念
|
||||
UI 测试:属于端到端测试,是从应用程序启动到结束的测试过程。完全按照用户与应用程序交互的方式来复制与应用程序的交互。比但愿测试慢得多,运行起来更消耗资源。
|
||||
|
||||
### 测试原则
|
||||
|
||||
FIRST
|
||||
- Fast:测试模块应该是快速高效的
|
||||
- Independent/Isolated:测试模块应该是独立、互相不影响的
|
||||
- Repeatable:测试实例应该是可以重复使用的,测试结果应该是相同的
|
||||
- Self-validating:测试应完全自动化。输出结果要么成功、要么失败
|
||||
- Timely:理想情况下,应该在编写要测试的生产代码之前编写测试(测试驱动开发)
|
||||
|
||||
### 需要测试什么?
|
||||
- 视觉表现验证
|
||||
- 元素渲染:控件尺寸、颜色、字体、图标资源(accessibilityIdentifier 定位)
|
||||
- 布局规则:动态布局(Auto Layout 约束断裂检测)、横竖屏适配、多语言截断
|
||||
- 动效完整性:转场动画时长、交互反馈(如按钮点击态)
|
||||
|
||||
- 交互行为验证
|
||||
|
||||
| 交互类型 | 测试要点 | 工具示例 |
|
||||
| :----------- | :----------------------------------------------- | :------------------------------------- |
|
||||
| **手势操作** | 滑动/长按/捏合等触发事件 | `XCUITest: swipeUp()` |
|
||||
| **表单输入** | 键盘类型切换、输入校验(正则)、自动填充 | `typeText("test@email.com")` |
|
||||
| **导航流** | 页面跳转栈深度、返回逻辑(物理返回 vs 程序返回) | `navigationBars.buttons["Back"].tap()` |
|
||||
| **异步状态** | 加载中/空状态/错误页的显示与隐藏 | `waitForExistence(timeout: 5)` |
|
||||
|
||||
- 数据驱动验证
|
||||
- API 数据映射:Mock 不同 API 响应(200/404/500),检查 UI 渲染正确性
|
||||
- 本地数据同步:Core Data/Realm 更新后 UI 即时刷新
|
||||
- 动态内容:富文本(含超链接)、图片懒加载、视频播放器状态
|
||||
- 边界场景验证(Edge Cases)
|
||||
- 设备兼容:从 iPhone SE 到 iPad Pro 的适配
|
||||
- 系统版本:iOS 14~17 的关键行为差异(如权限弹窗样式)
|
||||
- 极端操作:快速连续点击、低内存告警恢复
|
||||
- 无障碍支持:VoiceOver 焦点顺序、Dynamic Type 超大字体布局
|
||||
|
||||
|
||||
|
||||
### 基础使用
|
||||
|
||||
上面文章大篇幅的讲了单元测试相关的话题,单元测试十分适合代码质量、逻辑、网络等内容的测试,但是针对最终产物 App 来说单元测试就不太适合了,如果测试 UI 界面的正确性、功能是否正确显然就不太适合了。Apple 在 Xcode 7 开始推出的 `UI Testing` 就是苹果自己的 UI 测试框架。
|
||||
@@ -1339,16 +1378,194 @@ int main(int argc, char * argv[]) {
|
||||
|
||||
|
||||
|
||||
### 探索
|
||||
### 单元测试的原理窥探
|
||||
|
||||
开发的单元测试代码,运行的背后也是一个可执行文件。查看内部,可以发现一堆和测试相关的 framework。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/XcodeUnitTestDyanimcFramework.png" style="zoom:30%" />
|
||||
|
||||
思考:一个工程中,只要写好了测试代码,是不是加载这几个动态库就可以运行测试用例了?
|
||||
思考:一个非 UI 测试工程(正常的 App 工程),是不是加载这几个测试相关的动态库,写好测试代码,就可以运行测试用例了?
|
||||
|
||||
细节不贴了。Demo App 引入 `XCTest.framework` 后业务代码里即可引入 `#import <XCTest/XCTest.h>` 然后就可以编写测试代码了。
|
||||
|
||||
写法1:
|
||||
|
||||
```objective-c
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
// 测试管理集
|
||||
XCTestSuite *suite = [XCTestSuite defaultTestSuite];
|
||||
// 初始化 TestCase
|
||||
LoginUITests *loginTest = [LoginUITests testCaseWithSelector:@selector(testDidClickLoginAction)];
|
||||
|
||||
// 添加测试用例到当前 suite
|
||||
[suite addTest:loginTest];
|
||||
|
||||
// 遍历并运行测试用例
|
||||
for (XCTest *test in suite.tests) {
|
||||
[test runTest];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
写法2:
|
||||
|
||||
```objective-c
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
// 测试管理集
|
||||
XCTestSuite *suite = [XCTestSuite testSuiteForTestCaseClass:LoginUITests.class];
|
||||
// 初始化 TestCase
|
||||
LoginUITests *loginTest = [LoginUITests new];
|
||||
|
||||
// 添加测试用例到当前 suite
|
||||
[suite addTest:loginTest];
|
||||
|
||||
// 遍历并运行测试用例
|
||||
for (XCTest *test in suite.tests) {
|
||||
[test runTest];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 执行时机
|
||||
|
||||
这种情况下,整体流程为:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[测试套件启动] --> B[创建测试类实例]
|
||||
B --> C1[执行 testMethod1]
|
||||
C1 --> D1[调用 setUp]
|
||||
D1 --> E1[执行 testMethod1 本体]
|
||||
E1 --> F1[调用 tearDown]
|
||||
|
||||
B --> C2[执行 testMethod2]
|
||||
C2 --> D2[调用 setUp]
|
||||
D2 --> E2[执行 testMethod2 本体]
|
||||
E2 --> F2[调用 tearDown]
|
||||
|
||||
B --> C3[执行 testMethod3]
|
||||
C3 --> D3[调用 setUp]
|
||||
D3 --> E3[执行 testMethod3 本体]
|
||||
E3 --> F3[调用 tearDown]
|
||||
```
|
||||
|
||||
分析:
|
||||
|
||||
- 当有多个测试方法时,XCTest 会为**每个测试方法创建单独的类实例**
|
||||
|
||||
- 每个测试方法执行时都会触发完整的生命周期
|
||||
|
||||
```objective-c
|
||||
// 伪代码展示 XCTest 内部执行流程
|
||||
for (XCTest *test in allTests) {
|
||||
[test invokeTest]; // 实际执行入口
|
||||
}
|
||||
|
||||
// invokeTest 内部实现:
|
||||
- (void)invokeTest {
|
||||
[self setUp]; // 每次测试前调用
|
||||
[self performTest]; // 执行测试方法本体
|
||||
[self tearDown]; // 每次测试后调用
|
||||
}
|
||||
```
|
||||
|
||||
- 三个测试方法的执行示例
|
||||
|
||||
测试代码:
|
||||
|
||||
```objective-c
|
||||
- (void)testValidLogin { ... }
|
||||
- (void)testInvalidPassword { ... }
|
||||
- (void)testNetworkErrorHandling { ... }
|
||||
```
|
||||
|
||||
实际执行顺序
|
||||
|
||||
```objective-c
|
||||
// 测试1
|
||||
[LoginUITests setUp];
|
||||
[LoginUITests testValidLogin];
|
||||
[LoginUITests tearDown];
|
||||
|
||||
// 测试2
|
||||
[LoginUITests setUp]; // 全新状态!
|
||||
[LoginUITests testInvalidPassword];
|
||||
[LoginUITests tearDown];
|
||||
|
||||
// 测试3
|
||||
[LoginUITests setUp]; // 再次重置状态
|
||||
[LoginUITests testNetworkErrorHandling];
|
||||
[LoginUITests tearDown];
|
||||
```
|
||||
|
||||
思考:为什么需要每次重置?
|
||||
|
||||
1. **测试隔离原则**
|
||||
- 防止测试间的状态污染
|
||||
- 确保每个测试都是独立可重复的
|
||||
2. **资源管理**
|
||||
- 每次 `tearDown` 释放测试占用的资源
|
||||
- 避免内存泄漏累积
|
||||
3. **环境一致性**
|
||||
- `setUp` 确保每次测试初始条件相同
|
||||
- 不受前次测试副作用影响
|
||||
|
||||
QA:单独执行每个测试方法,都会走 setup 和 teardown。按了快捷键 command + u,运行所有的测试 case,会执行几次 setup?比如 Login 有3个测试 case。点击后执行流程是什么样的?
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant X as XCTestRunner
|
||||
participant C as LoginUITests Class
|
||||
participant I1 as 实例1 (testA)
|
||||
participant I2 as 实例2 (testB)
|
||||
participant I3 as 实例3 (testC)
|
||||
|
||||
X->>C: 调用 +[LoginUITests setUp] (类方法)
|
||||
activate C
|
||||
|
||||
X->>I1: 创建实例1 (testA)
|
||||
activate I1
|
||||
I1->>I1: -setUp (实例方法)
|
||||
I1->>I1: -testA (测试方法)
|
||||
I1->>I1: -tearDown (实例方法)
|
||||
deactivate I1
|
||||
|
||||
X->>I2: 创建实例2 (testB)
|
||||
activate I2
|
||||
I2->>I2: -setUp (实例方法)
|
||||
I2->>I2: -testB (测试方法)
|
||||
I2->>I2: -tearDown (实例方法)
|
||||
deactivate I2
|
||||
|
||||
X->>I3: 创建实例3 (testC)
|
||||
activate I3
|
||||
I3->>I3: -setUp (实例方法)
|
||||
I3->>I3: -testC (测试方法)
|
||||
I3->>I3: -tearDown (实例方法)
|
||||
deactivate I3
|
||||
|
||||
X->>C: 调用 +[LoginUITests tearDown] (类方法)
|
||||
deactivate C
|
||||
```
|
||||
|
||||
分析:
|
||||
|
||||
- 类级别初始化(只执行一次)。`+[LoginUITests setUp]` 类方法
|
||||
- 在**所有测试开始前**执行一次
|
||||
- 适合做全局初始化(如启动模拟服务器)
|
||||
- 执行频率:1次/测试类
|
||||
- 每个测试方法的独立执行。对于每个测试方法(testA, testB, testC):
|
||||
- 创建**新的测试类实例**
|
||||
- **-setUp** 实例方法(每个测试方法前执行)
|
||||
- 执行测试方法(如 **-testA**)
|
||||
- **-tearDown** 实例方法(每个测试方法后执行)
|
||||
- 类级别清理(只执行一次)。`+[LoginUITests tearDown]` 类方法
|
||||
- 在**所有测试结束后**执行一次
|
||||
- 适合做全局清理(如关闭模拟服务器)
|
||||
- 执行频率:1次/测试类
|
||||
|
||||
|
||||
|
||||
## 六、精准测试
|
||||
@@ -1369,7 +1586,7 @@ int main(int argc, char * argv[]) {
|
||||
|
||||
下面是之前在有赞,开发完精准测试系统后,落地到一个业务项目中取得的价值,帮助2位 QA 发现漏测的代码,倒逼 QA 去设计更完善的测试 case、分析覆盖率低是开发者的兜底代码太多还是真的漏掉了业务 case。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOS_PreciousUnitTest1.png" style="zoom:20%;display:inline"> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOS_PreciousUnitTest2.png" style="zoom:20%;display:inline">
|
||||
<img src="./../assets/iOS_PreciousUnitTest1.png" style="zoom:20%;display:inline"> <img src="./../assets/iOS_PreciousUnitTest2.png" style="zoom:20%;display:inline">
|
||||
|
||||
精准测试助力业务,质量更加稳定。
|
||||
|
||||
@@ -1394,3 +1611,5 @@ UITesting 还是建议在核心逻辑且长时间没有改动的情况下去做
|
||||

|
||||
|
||||
WWDC 这张图也很清楚,UI 其实需要的占比较小,还是要靠单测驱动。
|
||||
|
||||
##
|
||||
Reference in New Issue
Block a user