1.基础理论
1.1 单元测试&集成测试&系统测试
①单元测试
单元测试的核心目标是验证程序中最小的可测试单元(通常是函数、方法或类)是否按照预期工作。它关注的是单个逻辑单元在隔离环境下的正确性。
核心测试内容包括:
-
基本功能:
-
内容: 验证函数在给定标准、有效的输入时,是否能产生正确的输出。这是最基础的测试。
-
举例: 测试一个
add(x, y)
函数。-
test_add_positive_numbers()
:输入(2, 3)
,期望输出5
。 -
test_add_negative_numbers()
:输入(-1, -4)
,期望输出-5
。 -
test_add_zero()
:输入(5, 0)
,期望输出5
。
-
-
-
边界条件:
-
内容: 测试输入值处于有效范围边缘或刚好超出范围时的情况。这是错误最容易发生的地方(如:最小值、最大值、空值、零值、刚刚超过最大/最小值)。
-
举例: 测试一个
calculate_discount(order_total)
函数,折扣规则:满100
减10
。-
test_discount_at_threshold()
:输入100
,期望输出90
(正好达到边界)。 -
test_discount_just_below_threshold()
:输入99.99
,期望输出99.99
(未达到边界,无折扣)。 -
test_discount_zero_order()
:输入0
,期望输出0
(边界值:零处理)。 -
test_discount_negative_order()
:输入-50
,期望抛出InvalidOrderTotalException
(边界外:无效输入处理)。 -
测试一个处理字符串的函数
truncate(text, max_length)
:-
test_truncate_exact_length()
:输入text="Hello", max_length=5
,期望输出"Hello"
。 -
test_truncate_one_over()
:输入text="Hello!", max_length=5
,期望输出"Hello"
。 -
test_truncate_empty_string()
:输入text="", max_length=10
,期望输出""
(边界:空输入)。
-
-
-
-
错误处理和异常路径:
-
内容: 验证当函数接收到无效输入、遇到意外条件或依赖失败时,是否能以预期的方式处理错误(如:抛出正确的异常、返回错误码、记录日志)。
-
举例:
-
测试
divide(a, b)
函数:-
test_divide_by_zero()
:输入(10, 0)
,期望抛出ZeroDivisionError
异常。
-
-
测试一个从数据库读取用户的函数
get_user_by_id(user_id)
:-
test_get_user_invalid_id_format()
:输入user_id="abc"
(期望是数字),期望抛出InvalidIdException
。 -
test_get_user_not_found()
:输入一个不存在的user_id=9999
,期望返回null
或抛出UserNotFoundException
(取决于设计)。
-
-
测试一个需要配置文件的函数
load_config(config_path)
:-
test_load_config_file_not_found()
:输入一个不存在的文件路径,期望抛出FileNotFoundException
。
-
-
-
-
算法和逻辑路径:
-
内容: 确保代码中不同的条件分支(如
if/else
,switch/case
)和循环都按预期执行。需要设计输入数据来覆盖所有可能的路径。 -
举例: 测试一个判断成绩等级的函数
get_grade(score)
:-
test_grade_A()
:输入95
,期望输出'A'
。 -
test_grade_B()
:输入85
,期望输出'B'
。 -
test_grade_C()
:输入75
,期望输出'C'
。 -
test_grade_D()
:输入65
,期望输出'D'
。 -
test_grade_F()
:输入55
,期望输出'F'
。 -
test_grade_boundary_B_C()
:输入80
(假设 B>=80, C>=70),期望输出'B'
。 -
test_grade_invalid_high()
:输入110
,期望抛出InvalidScoreException
。 -
test_grade_invalid_low()
:输入-10
,期望抛出InvalidScoreException
。
-
-
-
状态变化 (对于有状态的类/对象):
-
内容: 验证对象的方法调用是否正确地改变了对象的内部状态。
-
举例: 测试一个简单的
Counter
类:class Counter: def __init__(self): self.value = 0 def increment(self): self.value += 1 def decrement(self): if self.value > 0: self.value -= 1 def get_value(self): return self.value
-
test_initial_value()
:创建Counter
对象后,get_value()
应返回0
。 -
test_increment()
:调用increment()
一次后,get_value()
应返回1
。 -
test_multiple_increments()
:调用increment()
三次后,get_value()
应返回3
。 -
test_decrement()
:先调用increment()
两次,再调用decrement()
一次,get_value()
应返回1
。 -
test_decrement_below_zero()
:直接调用decrement()
,get_value()
应保持为0
(或根据设计可能抛出异常,测试需对应)。
-
-
-
依赖交互的模拟 (通过 Mocking/Stubbing):
-
内容: 当被测单元依赖于其他模块(如数据库、网络服务、文件系统、外部API)时,单元测试需要隔离这些依赖。使用 Mock 或 Stub 来模拟依赖的行为,专注于测试被测单元本身的逻辑。测试的是被测单元如何与它的契约(接口) 交互,而不是依赖的真实实现。
-
举例: 测试一个
UserService
类的register_user(username, email)
方法,该方法内部依赖一个UserRepository
来保存用户。class UserService: def __init__(self, user_repository): self.user_repository = user_repository def register_user(self, username, email): # 检查用户名是否已存在 (调用 user_repository.find_by_username) if self.user_repository.find_by_username(username): raise UsernameExistsException() # 创建用户对象 new_user = User(username, email) # 保存用户 (调用 user_repository.save) self.user_repository.save(new_user) return new_user
-
测试
test_register_user_success()
:-
使用 Mock 框架创建一个
mock_user_repository
。 -
设置
mock_user_repository.find_by_username(username)
返回None
(模拟用户名不存在)。 -
调用
user_service.register_user("alice", "alice@example.com")
。 -
验证
mock_user_repository.save(new_user)
被调用了一次,并且传入的new_user
对象具有正确的username
和email
。
-
-
测试
test_register_user_username_exists()
:-
设置
mock_user_repository.find_by_username(username)
返回一个已有的用户对象 (模拟用户名已存在)。 -
调用
register_user
时,期望抛出UsernameExistsException
。 -
验证
mock_user_repository.save()
没有被调用。
-
-
-
②集成测试
-
核心目标: 验证多个模块、组件或服务在连接或组合后,是否能按照设计正确交互、协同工作。重点在于接口、数据流、通信协议和模块间的依赖关系是否正确。
-
测试内容与举例:
-
模块/组件间接口:
-
内容: 检查模块间传递的数据格式、类型、顺序、边界值是否正确。一个模块的输出是否成为另一个模块的正确输入。
-
举例:
-
电商系统: 测试
购物车服务
将选中的商品列表和用户ID传递给订单服务
创建订单时:-
购物车服务传递的数据结构(如JSON)是否符合订单服务的预期?
-
商品ID、数量、价格等信息是否准确传递?
-
用户ID是否存在且有效?
-
如果购物车传递了无效数据(如商品ID不存在),订单服务是否返回预期的错误信息?
-
-
-
-
子系统间交互:
-
内容: 测试由多个模块组成的子系统作为一个整体,与其他子系统(或外部服务)的交互。
-
举例:
-
用户认证子系统: 测试当
登录模块
处理用户登录请求时:-
它是否正确调用了
用户数据库模块
查询用户凭证? -
验证成功后,是否正确地调用了
会话管理模块
创建会话令牌? -
如果数据库查询超时或失败,登录模块是否进行了合理的错误处理和重试(可能涉及
重试机制模块
)? -
登录模块返回给前端应用的响应格式是否正确?
-
-
-
-
数据流与状态一致性:
-
内容: 验证数据在多个模块间流动时是否保持一致、完整,状态变化是否正确传递。
-
举例:
-
库存管理系统: 测试
下单模块
成功创建订单后:-
它是否正确地通知了
库存扣减模块
? -
库存扣减模块是否准确地减少了对应商品的库存数量?
-
库存查询模块
是否立即能查询到更新后的库存数量?(测试数据一致性) -
如果扣减库存失败(如库存不足),下单模块是否正确地回滚了订单创建操作?(测试事务一致性)
-
-
-
-
与外部依赖的集成:
-
内容: 测试系统与外部服务(数据库、消息队列、第三方API、文件系统、缓存等)的集成是否正常工作。这通常需要使用 Test Doubles (Stubs/Mocks) 来模拟外部依赖的行为,或者使用 测试专用实例 (如内存数据库、Mock Server)。
-
举例:
-
支付系统: 测试
支付处理模块
调用第三方支付网关API
:-
当支付网关返回“支付成功”时,支付处理模块是否正确地更新了订单状态为“已支付”并触发了后续流程(如发货)?
-
当支付网关返回“支付失败”(如余额不足、网络超时)时,支付处理模块是否正确地处理了错误(记录日志、通知用户、允许重试)?
-
(测试中可能使用Mock Server来模拟支付网关的各种响应)
-
-
数据库集成: 测试
用户管理模块
对数据库的CRUD操作:-
创建新用户记录后,是否能在数据库中准确查询到?
-
更新用户信息(如邮箱)后,查询结果是否立即反映更新?
-
删除用户后,是否确实从数据库中移除?关联数据是否按预期处理(级联删除或设为无效)?
-
-
-
-
错误处理和恢复:
-
内容: 验证当一个模块出现故障或返回错误时,与之交互的其他模块是否能进行预期的错误处理、恢复或降级,避免级联故障。
-
举例:
-
新闻聚合应用: 测试当
外部新闻源API
不可用时:-
新闻获取模块
是否能检测到故障? -
它是否按照设计降级,转而从
本地缓存模块
获取最近的数据? -
是否向监控系统报告了错误?
-
当API恢复后,是否能自动或手动恢复正常数据获取?
-
-
-
③系统测试
-
核心目标: 将整个软件系统(包括所有集成好的硬件、软件、网络、数据等)视为一个完整的、可交付的产品,在模拟真实或接近真实的生产环境下进行测试。目的是验证系统整体是否满足规定的业务需求、功能规格和用户需求,以及非功能性需求(性能、安全、可靠性等)。
-
测试内容与举例:
-
功能性需求验证 (端到端业务流程):
-
内容: 执行完整的、真实的用户业务流程,验证系统是否按需求规格说明书(SRS)工作。
-
举例:
-
电商系统:
-
用户注册 -> 搜索商品 -> 加入购物车 -> 填写收货地址 -> 选择支付方式 -> 完成支付 -> 查看订单状态 -> 收货确认 -> 评价商品。 测试整个流程是否顺畅,数据在各环节是否正确流转和展示。
-
后台管理: 管理员登录 -> 审核新上架商品 -> 处理用户退货申请 -> 生成销售报表。测试后台管理功能是否完整有效。
-
-
银行系统: 用户登录网银 -> 查询账户余额 -> 向另一个账户转账 -> 确认转账成功并收到通知短信 -> 查看交易记录。测试核心金融交易流程的准确性和安全性。
-
-
-
非功能性需求验证:
-
性能测试:
-
内容: 评估系统在特定负载下的响应时间、吞吐量、资源利用率(CPU、内存、网络、磁盘)等。
-
举例: 模拟1000个用户同时进行商品搜索,要求95%的请求响应时间小于2秒。
-
-
负载测试 & 压力测试:
-
内容: 评估系统在超出正常负载或极限负载下的表现(何时性能下降?何时崩溃?如何恢复?)。
-
举例: 在促销活动期间,模拟5000个用户同时抢购100件限量商品,观察系统能否处理高并发,队列是否有效,是否出现超卖或系统崩溃。
-
-
稳定性/可靠性测试:
-
内容: 验证系统在长时间运行或特定压力下是否能保持稳定,无内存泄漏、资源耗尽等问题。
-
举例: 让系统持续运行72小时,模拟日常用户操作负载,监控关键指标是否稳定。
-
-
安全性测试:
-
内容: 评估系统抵御攻击的能力(如注入攻击、跨站脚本XSS、身份认证绕过、数据泄露等)。
-
举例: 尝试使用SQL注入绕过登录;测试用户会话令牌是否容易伪造;检查敏感信息(密码、信用卡号)是否加密存储和传输。
-
-
兼容性测试:
-
内容: 验证系统在不同环境(操作系统、浏览器、设备、数据库版本、屏幕分辨率等)下的表现。
-
举例: 网站在Chrome, Firefox, Safari, Edge最新版以及特定移动设备(iOS, Android)上,核心功能是否正常,布局是否合理。
-
-
易用性/用户体验测试:
-
内容: 评估用户界面是否直观、易学、高效、符合用户习惯和期望(通常需要真实用户参与)。
-
举例: 新用户能否在30秒内找到“修改密码”的功能?结账流程的步骤是否清晰且必要?错误提示信息是否友好易懂?
-
-
配置测试:
-
内容: 验证系统在不同配置(如不同的数据库连接参数、缓存大小、服务器数量)下是否能正常工作。
-
举例: 测试系统在集群部署(多台应用服务器、负载均衡器)下的表现。
-
-
恢复性测试:
-
内容: 验证系统在发生故障(硬件故障、网络中断、服务崩溃)后,能否按照预期恢复数据和状态。
-
举例: 模拟数据库服务器宕机,启用备份数据库后,系统是否能继续提供服务且数据损失在可接受范围内(RPO/RTO)。
-
-
-
安装/卸载/升级测试:
-
内容: 测试软件安装包(或部署脚本)能否成功安装/部署系统;已安装的系统能否干净卸载;旧版本能否顺利升级到新版本(数据迁移、配置兼容)。
-
举例: 测试新版本的后台管理系统升级脚本,是否能正确迁移用户权限配置数据且不影响现有功能。
-
总结三者关键区别
特性 | 单元测试 (Unit Testing) | 集成测试 (Integration Testing) | 系统测试 (System Testing) |
---|---|---|---|
测试对象 | 单个函数/方法/类 | 多个模块/组件/服务 间的交互 | 整个集成好的系统 |
主要目标 | 验证内部逻辑正确性 | 验证接口/交互正确性 | 验证整体功能、需求、用户体验、非功能 |
关注点 | 输入输出、边界条件、分支覆盖 | 数据流、通信协议、依赖管理、错误传播 | 端到端业务流程、性能、安全、兼容、稳定 |
环境 | 隔离环境 (Mock/Stub) | 部分集成环境 (可能用Test Double) | 模拟/类生产环境 (真实或接近真实) |
执行者 | 开发者 | 开发者 / 测试工程师 | 测试工程师 (常需要自动化框架) |
粒度 | 最细 | 中等 | 最粗 |
发现缺陷 | 代码逻辑错误 | 接口不匹配、数据不一致、集成逻辑错误 | 需求理解偏差、业务流程缺陷、性能瓶颈、安全问题 |
1.2 接口测试
接口测试是验证软件系统不同组件间通信契约(API)正确性的测试活动,聚焦于服务/模块/系统间的交互点。它位于单元测试和UI测试之间,是集成测试的核心手段,尤其在微服务、前后端分离架构中至关重要。
接口测试的核心目标:
-
确保接口功能按设计规范工作(请求、响应、业务逻辑)。
-
验证接口的健壮性(对异常输入、错误条件的处理)。
-
保障接口的安全性(认证、授权、防攻击)。
-
检查接口的性能(响应时间、吞吐量)。
-
保证接口的可靠性和稳定性。
接口测试的核心内容与详细举例:
①基础功能验证
-
内容: 验证接口在提供合法有效输入时,是否能返回预期的正确响应(状态码、响应体数据)。
-
举例 (RESTful API - 用户注册):
-
请求:
-
POST /api/users
-
Headers:
Content-Type: application/json
-
Body:
{ "username": "alice123", "email": "alice@example.com", "password": "Str0ngP@ss!" }
-
-
预期响应:
-
Status Code:
201 Created
-
Response Body:
{ "id": 1001, "username": "alice123", "email": "alice@example.com", "message": "User created successfully" }
(可能包含创建的用户信息或部分信息)
-
-
测试点: 验证状态码为201,响应体包含预期的成功信息和必要字段(如id, username, email),且数据与请求一致。
-
②请求参数验证
-
内容: 测试接口对请求参数(路径参数、查询参数、请求体字段)的校验规则,包括必填项、数据类型、格式、长度、取值范围、枚举值等。
-
举例 (查询用户信息 API):
-
请求:
-
GET /api/users/{userId}
-
userId
是路径参数。
-
-
测试用例:
-
有效参数:
GET /api/users/1001
-> 期望200 OK
并返回用户1001的信息。 -
无效数据类型:
GET /api/users/abc
(userId需为数字) -> 期望400 Bad Request
+ 错误信息{"error": "Invalid user ID format. Must be a number."}
。 -
参数缺失:
GET /api/users/
(缺少userId) -> 期望404 Not Found
或400 Bad Request
(取决于路由设计)。 -
越界值:
GET /api/users/9999999
(用户不存在) -> 期望404 Not Found
+ 错误信息{"error": "User not found."}
。
-
-
-
举例 (创建订单 API - 请求体字段):
-
必填项缺失: 请求体中缺少
productId
-> 期望400 Bad Request
+ 错误信息指明缺少必填字段。 -
数据格式错误:
quantity
字段传入字符串"two"
(应为整数) -> 期望400 Bad Request
+ 错误信息指明格式错误。 -
长度限制:
shippingAddress
字段传入超过255个字符的字符串 -> 期望400 Bad Request
+ 错误信息指明超出长度限制。 -
枚举值不符:
paymentMethod
字段传入"bitcoin"
(只支持"credit_card"
,"paypal"
) -> 期望400 Bad Request
+ 错误信息指明无效枚举值。
-
③响应数据验证
-
内容: 验证响应数据的结构、数据类型、值、格式是否符合约定(如JSON Schema, Swagger/OpenAPI定义)。包括字段是否存在、是否为空(null/空字符串/空数组)、数据格式(日期时间、邮箱、URL等)、业务逻辑计算结果的正确性。
-
举例 (查询订单详情 API):
-
请求:
GET /api/orders/2001
-
预期响应结构 (部分):
json
复制
下载
{ "id": 2001, "userId": 1001, "status": "SHIPPED", "totalAmount": 99.99, "createdAt": "2024-05-15T10:30:00Z", // ISO 8601 格式 "items": [ { "productId": 3001, "productName": "Widget", "quantity": 2, "unitPrice": 49.99, "subtotal": 99.98 // 应等于 quantity * unitPrice } ] }
-
测试点:
-
验证所有预期字段都存在且名称正确。
-
验证
id
,userId
是整数,status
是字符串且在预期枚举内,totalAmount
是数字,createdAt
是符合ISO 8601的字符串。 -
验证
items
数组非空,数组内对象的字段结构正确。 -
验证
items[0].subtotal
值等于items[0].quantity * items[0].unitPrice
(业务逻辑)。 -
验证
totalAmount
值等于所有items.subtotal
之和 (业务逻辑)。
-
-
④错误处理与异常状态码
-
内容: 验证接口在遇到错误或异常情况时(如无效输入、资源不存在、权限不足、服务器内部错误、依赖服务失败),是否能返回正确的HTTP状态码和清晰、一致、有用的错误信息。
-
举例:
-
未授权访问: 访问需要认证的接口
GET /api/admin/users
但未提供有效Token -> 期望401 Unauthorized
+ 错误信息{"error": "Authentication required"}
。 -
权限不足: 普通用户尝试删除其他用户信息
DELETE /api/users/1002
-> 期望403 Forbidden
+ 错误信息{"error": "Forbidden: You do not have permission to perform this action"}
。 -
资源不存在:
GET /api/products/9999
(产品不存在) -> 期望404 Not Found
+ 错误信息{"error": "Product not found"}
。 -
请求体格式错误: 发送的JSON格式不正确(如缺少引号、括号不匹配)-> 期望
400 Bad Request
+ 错误信息{"error": "Malformed JSON request body"}
。 -
服务器内部错误: 模拟数据库连接失败 -> 期望
500 Internal Server Error
+ 通用错误信息(避免泄露敏感细节){"error": "An internal server error occurred. Please try again later."}
。
-
⑤安全测试
-
内容: 验证接口的安全性控制措施是否有效。
-
关键测试点:
-
认证(Authentication):
-
未认证用户访问受保护接口 -> 应返回
401
。 -
使用无效/过期Token访问 -> 应返回
401
。 -
认证机制本身的安全性(如JWT签名验证、Token泄露风险)。
-
-
授权(Authorization):
-
低权限用户尝试执行高权限操作(如普通用户尝试访问管理员接口)-> 应返回
403
。 -
尝试越权访问/操作他人数据(如用户A尝试修改用户B的资料)-> 应返回
403
或404
(取决于设计)。
-
-
输入安全防护:
-
SQL注入: 在参数中尝试注入SQL片段(如
productId=1 OR 1=1--
)-> 期望接口能防御,返回错误或空结果,绝不执行注入语句。 -
XSS (跨站脚本): 在参数中尝试注入HTML/JS脚本(如
username=<script>alert('xss')</script>
)-> 期望响应中该值被正确转义或过滤,不会在浏览器端执行。 -
敏感数据泄露: 检查响应中是否包含不应返回的敏感信息(如明文密码、完整信用卡号、内部系统路径、堆栈跟踪)。
-
参数篡改: 尝试修改请求参数(如订单ID、用户ID、价格)以进行未授权操作 -> 应被授权检查拦截 (
403
) 或业务逻辑校验失败。
-
-
其他: HTTPS强制使用、CSRF防护(如果适用)、速率限制(防暴力破解)。
-
⑥性能测试 (通常作为专项,但接口测试会关注基础性能)
-
内容: 在接口层面评估响应速度、吞吐量和资源消耗。
-
测试点:
-
单请求响应时间: 在低负载下,一个典型请求的完成时间(如应 < 500ms)。
-
吞吐量: 单位时间内接口能成功处理的最大请求数(如TPS - Transactions Per Second)。
-
稳定性: 在持续负载下,接口是否稳定,有无错误率上升、响应时间陡增或内存泄漏。
-
并发能力: 模拟多个用户同时调用接口,检查是否出现死锁、资源竞争、数据不一致。
-
示例: 使用 JMeter 或 Locust 模拟 100 个并发用户持续 5 分钟调用
GET /api/products
,监控平均响应时间(<1s)、错误率(<0.1%)、吞吐量(>50 TPS)。
-
⑦业务逻辑验证
-
内容: 通过组合调用多个相关接口,验证跨越单个接口边界的复杂业务规则和流程是否得到正确执行。
-
举例 (电商下单流程):
-
调用A (添加购物车):
POST /api/cart/items
-> 成功 (201
)。 -
调用B (创建订单):
POST /api/orders
(引用购物车) -> 成功 (201
),返回orderId
。 -
调用C (扣减库存): (通常是订单服务内部调用库存接口,但也可显式测试库存接口状态) -> 验证对应商品库存已减少。
-
调用D (支付订单):
POST /api/orders/{orderId}/payments
-> 成功 (200
)。 -
验证: 调用
GET /api/orders/{orderId}
确认状态变为PAID
;调用库存接口确认扣减无误且未超卖;调用支付记录接口确认支付成功。验证整个链路数据一致性。
-
⑧依赖与集成点验证
-
内容: 测试接口如何与它依赖的外部服务(数据库、消息队列、缓存、第三方API)或内部其他服务交互。常使用Mock或Stub隔离依赖进行测试。
-
举例:
-
第三方支付网关: Mock支付网关的响应(成功、失败、超时),测试订单服务的支付接口如何正确处理各种情况(更新订单状态、触发退款等)。
-
数据库: 测试接口的CRUD操作是否产生正确的数据库记录和状态变更。
-
消息队列: 验证接口在处理后是否向正确的队列发送了预期格式的消息(如订单创建后发送
OrderCreatedEvent
)。
-
⑨幂等性测试 (对POST/PUT/PATCH/DELETE操作尤为重要)
-
内容: 验证同一个请求(或语义相同的请求,如网络超时重试)被重复执行多次时,是否产生与执行一次相同的效果(状态和数据)。这对保证系统可靠性和应对网络问题至关重要。
-
举例 (创建订单 - 期望幂等):
-
第一次调用
POST /api/orders
(带唯一幂等键X-Idempotency-Key: key123
) -> 成功创建订单201
, 返回orderId: 2001
。 -
使用完全相同的请求(含相同的
X-Idempotency-Key: key123
)再次调用 -> 期望返回200 OK
或201 Created
,并返回同一个orderId: 2001
,而不是创建新订单。数据库中也只应有一条订单记录。
-
⑩异步接口测试
-
内容: 测试触发异步操作的接口(如提交长时间任务、发送消息)和查询异步操作结果的接口。
-
举例 (文件处理服务):
-
调用A (提交处理):
POST /api/files/process
-> 立即返回202 Accepted
和一个taskId
。 -
调用B (查询状态): 轮询
GET /api/tasks/{taskId}
。-
状态可能是
PENDING
,PROCESSING
,SUCCEEDED
(返回结果URL),FAILED
(返回错误信息)。
-
-
测试点:
-
提交接口是否正确返回
202
和taskId
。 -
状态查询接口是否准确反映任务状态。
-
任务成功后,是否能通过返回的URL获取到正确结果。
-
任务失败时,是否返回清晰的错误原因。
-
超时处理是否合理。
-
-
接口测试常用工具:
-
手工测试/探索性测试: Postman, Insomnia, HTTPie, cURL, 浏览器开发者工具。
-
自动化测试:
-
代码框架: RestAssured (Java), Requests + Pytest (Python), Supertest (Node.js), HttpClient + xUnit/NUnit (C#), Retrofit (Android), Alamofire (iOS)。
-
低代码/可视化工具: Postman (Collections + Runner), Katalon Studio, SoapUI/ReadyAPI (尤其擅长SOAP)。
-
性能测试: JMeter, Locust, k6, Gatling。
-
Mock工具: WireMock, MockServer, Postman Mock Servers, Mountebank。
-
持续集成集成: Jenkins, GitLab CI, GitHub Actions, Azure Pipelines (与上述工具结合)。
-
1.3 负载测试&压力测试&性能测试
①性能测试 (Performance Testing)
-
目标: 全面评估系统的性能指标(如响应时间、吞吐量、资源利用率),确保系统在预期负载下满足性能需求。
-
关注点:
-
响应时间(用户请求的处理速度)。
-
吞吐量(单位时间内处理的请求数,如 TPS - Transactions Per Second)。
-
资源消耗(CPU、内存、磁盘 I/O、网络带宽)。
-
系统稳定性(在负载下是否能持续运行)。
-
可伸缩性(增加资源是否能线性提升性能)。
-
-
测试范围: 最广泛,是负载测试和压力测试的超集。它包含多种子类型:
-
负载测试 (Load Testing) - 核心部分
-
压力测试 (Stress Testing) - 核心部分
-
容量测试 (Capacity Testing)
-
并发测试 (Concurrency Testing)
-
配置测试 (Configuration Testing)
-
可靠性测试 (Reliability Testing)
-
-
典型场景:
-
模拟正常或预期高峰负载(如电商双11流量)。
-
回答:“系统在每天 10,000 个并发用户下的响应时间是多少?”
-
-
关键问题:
-
系统在目标负载下表现如何?
-
是否满足 SLA(服务等级协议)?
-
②负载测试 (Load Testing)
-
目标: 验证系统在预期(或略高于预期)负载下的性能和行为。目的是确认系统能否处理设计时的日常或高峰流量。
-
关注点:
-
系统在目标负载下是否稳定(不崩溃、无内存泄漏)。
-
响应时间是否在可接受范围内。
-
吞吐量是否达到预期。
-
资源使用(CPU、内存等)是否合理,无瓶颈。
-
-
负载水平: 通常在预期负载(设计容量)到略高于预期负载(如 120%)之间。
-
停止条件: 达到目标负载并稳定运行一段时间。
-
典型场景:
-
模拟 1,000 个用户同时浏览商品页面。
-
验证系统能否处理每月账单生成日的峰值交易量。
-
-
关键问题:
-
系统能承受设计时的最大用户量吗?
-
在正常压力下,性能是否达标?
-
③压力测试 (Stress Testing)
-
目标: 将系统推到超出其设计容量的极限,直到它崩溃。目的是发现系统的崩溃点和失效模式,并观察其恢复能力。
-
关注点:
-
系统何时开始失效?(如错误率飙升、响应时间剧增)。
-
如何失效?(是优雅降级、部分功能不可用,还是彻底崩溃?)。
-
失效后的恢复能力(能否自动恢复?恢复需要多久?数据是否一致?)。
-
在极端负载下暴露的隐藏缺陷(如资源泄漏、线程死锁、数据库连接池耗尽)。
-
-
负载水平: 远超系统设计容量(如 200%、500% 甚至更高),持续增加直到系统崩溃。
-
停止条件: 系统完全崩溃或性能严重恶化到不可用状态。
-
典型场景:
-
突然涌入 10 倍于日常流量的用户(如突发新闻事件)。
-
模拟秒杀活动中远超库存量的抢购请求。
-
持续加压直到数据库连接池耗尽或服务器宕机。
-
-
关键问题:
-
系统的极限在哪里?
-
崩溃后会发生什么?能否快速恢复?
-
在极端压力下,系统是否会出现数据损坏或安全漏洞?
-
三者的核心区别总结
特性 | 性能测试 (Performance Testing) | 负载测试 (Load Testing) | 压力测试 (Stress Testing) |
---|---|---|---|
主要目标 | 全面评估性能指标 (响应时间、吞吐量、资源) | 验证系统在预期负载下的表现 | 找到系统极限和失效模式,测试恢复能力 |
负载范围 | 宽泛 (从低到极高负载) | 预期负载 (设计容量 ± 缓冲区) | 远超设计容量 (直到崩溃) |
关注重点 | 综合性能指标、稳定性、可伸缩性 | 稳定性、响应时间、吞吐量达标 | 崩溃点、失效模式、恢复能力 |
停止条件 | 完成预定场景或指标收集 | 达到目标负载并稳定运行 | 系统崩溃或严重失效 |
典型问题 | “系统在 X 负载下响应时间是多少?” | “系统能处理设计时的最大用户量吗?” | “系统在极端流量下会怎样崩溃?如何恢复?” |
类比 | 全面体检 (身高、体重、血压、耐力等) | 马拉松配速跑 (测试能否按计划完成) | 极限负重深蹲 (直到腿发抖或摔倒) |
实际工作流中的关系
-
性能测试 (整体框架): 定义性能目标(如:首页加载 < 2s,支持 1000 TPS)。
-
负载测试 (核心验证): 模拟 1000 TPS 的负载,确认响应时间 < 2s,资源使用正常。
-
压力测试 (探索极限): 逐步增加负载至 2000 TPS → 3000 TPS → ...,观察系统何时出错、如何出错、出错后能否恢复。
-
结果分析:
-
负载测试通过 → 系统满足设计需求。
-
压力测试暴露瓶颈(如 DB 连接池不足)→ 优化扩容。
-
压力测试发现恢复缺陷 → 改进高可用设计。
-
关键结论:
负载测试是性能测试的子集,关注“达标”;
压力测试是性能测试的极端子集,关注“崩溃与恢复”。
完整性能评估需三者结合,才能全面掌握系统能力。
1.4.缺陷相关
①bug状态流转
每个公司使用的工具略有不同,大致内容为:
- 测试人员提交新的 Bug 入库,错误状态为 New。
- 设置状态为 Open,分配 bug 至对应的模块开发人员。
- 开发人员查询状态为 Open 的 Bug,如果不是错误,则置状态为 Declined;如果是 Bug 则修复并置状态为 Fixed。不能解决的 Bug,要留下文字说明及保持 Bug 为 Open 状态。
- 对于不能解决和延期解决的 Bug,不能由开发人员自己决定,一般要通过某种会议(评审会)通过才能认可。
- 测试人员查询状态为 Fixed 的 Bug,然后验证 Bug 是否已解决,如解决,置 Bug 的状态为 Closed, 如没有解决,置 bug 状态为 Reopen。
-
②bug相关的内容
-
1.版本号,提交缺陷报告时通过该字段标识此缺陷存在于被测试软件的哪个版本。
2.Bug 报告优先级
3.Bug 状态
4.Bug 的编号
5.发现人
6.提交人
7.指定处理人
8.概述
9.从属关系
10.详细描述
11.严重程度
12.所属模块
13.附件
14.提交日期
-
1.5 Web 和 APP 测试的异同
-
单纯从功能测试的层面上来讲的话,APP 测试、web 测试 在流程和功能测试上是没有区别的。根据两者载体不一样,则区别如下:
系统结构方面:
web 项目,b/s 架构,基于浏览器的;web 测试只要更新了服务器端,客户端就会同步会更新。
app 项,c/s 结构的,必须要有客户端;app 修改了服务端,则客户端用户所有核心版本都需要进行一遍。性能方面:
web 项目 需监测 响应时间、CPU、Memory
app 项目 除了监测 响应时间、CPU、Memory 外,还需监测 流量、电量等兼容方面:
web 项目:
1.浏览器(火狐、谷歌、IE 等)
2.操作系统(Windows7、Windows10、Linux 等)
app 项目:
1.设备系统: iOS(ipad、iphone)、Android(三星、华为、联想等)
2.手机设备可根据 手机型号、分辨率不同相对于 web 项目,APP 有专项测试:
1.干扰测试:中断,来电,短信,关机,重启等
2.弱网络测试(模拟 2g、3g、4g,wifi 网络状态以及丢包情况);网络切换测试(网络断开后重连、3g 切换到 4g/wifi 等)
3.安装、更新、卸载
安装:需考虑安装时的中断、弱网、安装后删除安装文件等情况
2.Web测试相关理论
2.1 用浏览器访问 www.baidu.com 的过程
1. 输入URL与解析
-
浏览器解析URL:识别协议(默认HTTP/HTTPS)、域名(
www.baidu.com
)、路径(首页为/
)。 -
检查缓存:
-
浏览器缓存:检查是否缓存过该域名的DNS记录或页面资源。
-
系统缓存(如本地hosts文件):查看是否有手动配置的IP映射。
-
路由器缓存:查询本地路由器的DNS记录。
-
2. DNS域名解析
-
若缓存未命中,发起DNS查询:
-
本地DNS服务器(由ISP提供):查询自身缓存。
-
根域名服务器:返回
.com
顶级域的地址。 -
.com顶级域名服务器:返回
baidu.com
的权威DNS服务器地址。 -
权威DNS服务器(百度管理):返回
www.baidu.com
的实际IP(如110.242.68.4
)。
-
-
最终浏览器获得目标服务器的IP地址(整个过程通常耗时 几十毫秒)。
3. 建立TCP连接(三次握手)
-
步骤:
-
浏览器 → 服务器:发送
SYN
包(序列号x
)。 -
服务器 → 浏览器:回复
SYN-ACK
包(序列号y
,确认号x+1
)。 -
浏览器 → 服务器:发送
ACK
包(确认号y+1
)。
-
-
目的:确保双方具备收发能力,建立可靠传输通道(耗时约 1~3个RTT)。
注:若使用HTTPS(百度默认强制跳转),后续需进行TLS握手(见步骤4)。
4. TLS加密握手(HTTPS)
-
流程:
-
浏览器发送
ClientHello
:支持的TLS版本、加密套件、随机数。 -
服务器回复
ServerHello
:选定加密套件、随机数 + 服务器证书(含公钥)。 -
证书验证:浏览器检查证书有效期、颁发机构(CA)、域名匹配性(防止钓鱼)。
-
生成会话密钥:浏览器用证书公钥加密 预主密钥 → 发送给服务器。
-
双方根据随机数和预主密钥生成对称加密密钥(用于后续数据传输)。
-
-
结果:建立安全加密通道(耗时约 1~2个RTT)。
5. 发送HTTP请求
-
浏览器通过TCP连接发送 HTTP GET请求:
GET / HTTP/1.1 Host: www.baidu.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Accept: text/html,application/xhtml+xml Accept-Encoding: gzip, deflate, br Connection: keep-alive Cookie: BAIDUID=...(如有缓存Cookie)
6. 服务器处理请求
-
负载均衡:请求可能先到达百度的负载均衡器(如Nginx/LVS),分配到后端服务器集群。
-
生成响应:
-
静态资源(如图片/CSS)直接读取CDN或本地缓存。
-
动态内容:通过后端服务(如C++/Python程序)生成HTML。
-
-
响应格式:
HTTP/1.1 200 OK Server: BWS/1.0 Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Set-Cookie: BDORZ=...; path=/; domain=.baidu.com <!DOCTYPE html><html>...</html> (压缩后的HTML内容)
7. 浏览器解析与渲染
-
解析HTML:构建DOM树(Document Object Model)。
-
加载子资源:
-
解析到
<link>
,<script>
,<img>
标签时,发起 并发HTTP请求(浏览器限制同一域名6~8个并行)。 -
静态资源(JS/CSS/图片)可能来自CDN(如
s1.bdstatic.com
)。
-
-
解析CSS:构建CSSOM树(CSS Object Model)。
-
执行JavaScript:
-
遇到
<script>
可能阻塞渲染(除非标记async/defer
)。 -
百度首页的JS负责交互逻辑(如搜索框建议)。
-
-
构建渲染树(Render Tree):合并DOM与CSSOM,确定可见元素布局。
-
绘制(Painting):将渲染树转换为像素,显示到屏幕。
8. 连接终止
-
TCP四次挥手:
-
浏览器 → 服务器:
FIN
包(请求关闭)。 -
服务器 → 浏览器:
ACK
包(确认收到)。 -
服务器 → 浏览器:
FIN
包(服务器准备关闭)。 -
浏览器 → 服务器:
ACK
包(最终确认)。
-
-
连接关闭:释放TCP端口资源。
9.全过程时间线
2.2 SQL注入&XSS&CSRF
以下是针对 SQL注入、XSS(跨站脚本攻击) 和 CSRF(跨站请求伪造) 的预防方案,结合工程实践的关键措施【来自AI】:
1. SQL注入防御
核心思路: 隔离数据与代码,避免用户输入被解释为SQL指令。
关键措施:
方法 | 说明 | 代码示例(Python) |
---|---|---|
参数化查询 | 使用占位符绑定参数,数据库区分指令与数据 | cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) |
ORM框架 | 使用SQLAlchemy、Hibernate等,自动转义参数 | User.query.filter_by(id=user_id).first() |
输入过滤 | 白名单校验数据类型(如数字强制转int) | if not user_id.isdigit(): raise Error() |
最小化数据库权限 | 应用账户仅授予SELECT/UPDATE 必要权限,禁用DROP 等 | GRANT SELECT, UPDATE ON db.table TO 'app_user'@'%'; |
禁用动态拼接SQL | 严禁直接拼接SQL:"SELECT * FROM users WHERE name='" + name + "'" ❌ |
额外防护:
Web应用防火墙(WAF)配置SQL注入规则
定期扫描数据库(如SQLMap)
错误信息脱敏(避免泄露表结构)
2. XSS防御
核心思路: 禁止不可信数据被当作代码执行。
关键措施:
类型 | 防御方案 | 代码示例 |
---|---|---|
存储型/反射型XSS | 输出到HTML前进行转义 | Python: html.escape(user_input) JS: const safeStr = _.escape(text) |
前端渲染框架自动转义(React/Vue/Angular默认开启) | Vue: <div>{{ userContent }}</div> (自动转义) | |
DOM型XSS | 避免用innerHTML /document.write() ,改用textContent | document.getElementById("output").textContent = userInput; |
第三方库过滤:DOMPurify | const clean = DOMPurify.sanitize(dirtyHTML); | |
通用防护 | 设置HTTP头 Content-Security-Policy (CSP) 限制脚本源 | Content-Security-Policy: default-src 'self'; script-src https://trusted.cdn.com |
Cookie标记HttpOnly (阻止JS访问敏感Cookie) | Set-Cookie: sessionId=abc123; HttpOnly; Secure |
关键实践:
富文本使用白名单过滤(如JS-XSS库)
避免拼接HTML:
element.innerHTML = "<div>" + text + "</div>"
❌URL参数校验格式(如
location.href
跳转前检查域名)
3. CSRF防御
核心思路: 验证请求来源是否合法。
关键措施:
防御方案 | 原理 | 实现方式 |
---|---|---|
CSRF Token | 服务端生成随机Token,前端在表单/Header中提交,服务端校验匹配性 | 后端:Session中存储Token 前端: <input type="hidden" name="csrf_token" value="..."> 或Header: X-CSRF-Token: xxx |
SameSite Cookie | 限制Cookie随跨站请求发送 | Set-Cookie: sessionId=123; SameSite=Lax; (Strict/Lax/None) |
双重Cookie验证 | 前端读取Cookie值作为参数提交,服务端比对参数与Cookie中的值 | fetch("/api", { headers: { "X-CSRF-Cookie": getCookie("sessionId") } }) |
关键操作二次验证 | 敏感操作(如转账)要求密码/短信验证码 | 业务逻辑层强制验证 |
最佳实践组合:
SameSite=Lax
+CSRF Token
(应对现代浏览器兼容性)
避免方案:
❌ 依赖Referer检查(不可靠且可能泄露来源)
各漏洞检测工具
漏洞类型 | 检测工具 |
---|---|
SQL注入 | SQLMap, OWASP ZAP, Acunetix |
XSS | XSStrike, Burp Suite Scanner |
CSRF | CSRF Tester (Burp插件), 手动测试 |
2.3 https的工作原理
HTTPS(HyperText Transfer Protocol Secure)的工作原理本质上是 HTTP over TLS/SSL,通过在HTTP与TCP之间增加加密层,实现数据传输的机密性、完整性和身份认证。以下是其核心工作流程与技术细节:
一、核心目标
-
机密性:数据加密传输(防窃听)。
-
完整性:数据不被篡改(防篡改)。
-
身份认证:验证服务器真实性(防冒充)。
步骤1:协商安全参数
-
ClientHello:
浏览器发送支持的最高TLS版本(如TLS 1.2)、支持的加密套件列表(如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
)、随机数ClientRandom
。
步骤2:服务器响应
-
ServerHello:
服务器选定TLS版本和加密套件,生成随机数ServerRandom
。 -
Certificate:
发送服务器证书(包含域名、公钥、CA签名)。 -
ServerKeyExchange(可选):
若使用ECDHE等密钥交换算法,发送椭圆曲线参数和服务端临时公钥。
步骤3:客户端验证与密钥生成
-
证书验证:
浏览器检查证书:-
是否由可信CA签发(验证证书链)。
-
域名是否匹配(防钓鱼)。
-
是否在有效期内。
-
是否被吊销(CRL/OCSP校验)。
-
-
生成密钥:
客户端生成PreMasterSecret
,用服务器证书公钥加密后发送。 -
计算会话密钥:
双方用ClientRandom
+ServerRandom
+PreMasterSecret
生成相同的对称密钥(如AES密钥)。
步骤4:切换加密与完成握手
-
ChangeCipherSpec:
双方通知后续通信启用加密。 -
Finished:
发送加密的握手消息摘要(HMAC),验证握手过程是否被篡改。
2.4 cookie&session&token
Cookie、Session 和 Token 是 Web 开发中管理用户状态的三种核心技术,核心区别在于数据存储位置、安全性和适用场景。以下是详细对比:
一、核心区别总结
特性 | Cookie | Session | Token (如 JWT) |
---|---|---|---|
存储位置 | 客户端 (浏览器) | 服务端 (内存/数据库/Redis) | 客户端 (LocalStorage/Cookie) |
数据安全性 | ❌ 较低 (易被 XSS 窃取) | ✅ 较高 (敏感信息存服务端) | ⚠️ 中等 (需防 XSS,内容可能被解码) |
跨域支持 | ❌ 受同源策略限制 (需配置 CORS) | ✅ 天然支持 (服务端处理) | ✅ 可跨域 (通过 Authorization 头传递) |
服务端状态 | ❌ 无状态 | ✅ 有状态 (需存储 Session) | ❌ 无状态 (服务端不存储) |
扩展性 | ⚠️ 一般 (依赖浏览器) | ❌ 低 (集群需共享 Session) | ✅ 高 (天然支持分布式) |
典型应用场景 | 保存用户偏好、跟踪 ID | 传统服务端渲染应用 (如 PHP/JSP) | 前后端分离/API/移动端 (如 React + JWT) |
二、工作原理详解
1. Cookie
-
本质:
浏览器存储的小型文本(键值对,最大 4KB),每次请求自动携带在Cookie
头中。 -
关键属性:
-
HttpOnly
:禁止 JavaScript 访问(防 XSS) -
Secure
:仅 HTTPS 传输 -
SameSite
:限制跨站请求携带 Cookie(防 CSRF) -
Expires/Max-Age
:设置过期时间
-
2. Session
-
本质:
服务端存储的用户状态(如用户ID、权限),通过 Session ID 关联客户端。 -
服务端存储方案:
-
内存(单机适用)
-
Redis(集群推荐,高性能)
-
数据库(不推荐,性能差)
-
3. Token (以 JWT 为例)
-
本质:
自包含的加密字符串(JSON 数据 + 签名),客户端存储,每次请求手动放入Authorization
头。 -
JWT 结构:
-
Header:算法类型 (如 HS256)
-
Payload:用户数据 (如
{ "user_id": "123", "exp": 1735689600 }
) -
Signature:防篡改签名 (服务器私钥生成)
-
-
工作流程:
服务器浏览器服务器浏览器登录请求 (用户名/密码)生成JWT返回 (不存储)请求头: Authorization: Bearer <token>验证签名 + 检查过期时间返回数据
三、安全对比与防御
攻击类型 | Cookie | Session | Token | 防御方案 |
---|---|---|---|---|
XSS | 高风险 (窃取 Cookie) | 低风险 (Session ID 可能被窃) | 高风险 (窃取 Token) | HttpOnly + 输入转义 + CSP |
CSRF | 高风险 (自动携带) | 高风险 | 免疫 (手动传 Token) | SameSite=Lax + CSRF Token |
数据泄露 | 敏感信息勿存 Cookie | 安全 (数据在服务端) | 避免敏感数据存 Payload | Payload 加密 + HTTPS |
2.5 TCP&UDP
TCP(传输控制协议)和UDP(用户数据报协议)是传输层的两大核心协议,本质区别在于是否建立连接和是否保证可靠性。以下是全方位对比:
一、核心区别总结
特性 | TCP | UDP |
---|---|---|
连接机制 | ✅ 面向连接(三次握手) | ❌ 无连接(直接发送) |
可靠性 | ✅ 高可靠(确认重传、有序交付) | ❌ 尽力交付(可能丢包、乱序) |
传输速度 | ❌ 较慢(需建立连接+确认机制) | ✅ 极快(无控制开销) |
数据边界 | ❌ 字节流(无消息边界) | ✅ 数据报(保留消息边界) |
拥塞控制 | ✅ 动态调整速率(避免网络阻塞) | ❌ 无控制(可能加剧拥塞) |
头部大小 | 20-60字节(复杂结构) | 8字节(固定简单) |
适用场景 | 文件传输、网页浏览、邮件 | 视频流、游戏、DNS、直播 |
3.App测试相关理论
3.1 安卓四大组件及作用
安卓四大组件是应用开发的核心基础,它们各自承担特定职责,共同协作实现应用功能:
1. Activity(活动)
-
作用:提供用户交互界面,负责与用户直接交互(如显示UI、处理触摸事件)。
-
关键特性:
-
每个界面通常对应一个Activity(如登录页、主页面)。
-
通过
startActivity()
或startActivityForResult()
启动。 -
具有生命周期(
onCreate()
,onResume()
,onPause()
等),系统根据用户操作管理其状态。
-
-
示例:微信的聊天窗口、通讯录列表均为独立Activity。
2. Service(服务)
-
作用:在后台执行长时间运行的操作(无界面),不阻塞用户交互。
-
类型:
-
前台服务:需显示通知(如音乐播放)。
-
后台服务:执行有限时长的任务(Android 8+限制后台服务)。
-
绑定服务:供其他组件调用其接口(如跨进程通信)。
-
-
启动方式:
-
startService()
:长期运行(如下载文件)。 -
bindService()
:与其他组件绑定交互(如获取天气数据)。
-
-
示例:后台播放音乐、定时同步数据。
3. BroadcastReceiver(广播接收器)
-
作用:监听系统或应用发出的全局事件,并作出响应。
-
工作模式:
-
系统广播:监听系统事件(如开机完成、网络变化)。
-
自定义广播:应用内部或跨应用通信。
-
-
注册方式:
-
静态注册:在
AndroidManifest.xml
声明(监听持久性事件,如开机启动)。 -
动态注册:代码中通过
registerReceiver()
注册(需随组件生命周期注销,如界面内监听网络变化)。
-
-
示例:监听电量不足提示、接收新短信通知。
4. ContentProvider(内容提供器)
-
作用:管理结构化数据的跨应用共享,提供统一访问接口(类似数据库)。
-
核心能力:
-
通过
ContentResolver
实现增删改查(CRUD)操作。 -
支持权限控制,保护数据安全。
-
数据源可以是SQLite、文件或网络。
-
-
示例:通讯录数据供其他应用读取、相册图片共享。
3.2 当点击 APP 图标启动程序,说明将要发生那些过程
阶段 | 餐厅比喻 | 安卓实际过程(简化版) |
---|---|---|
1. 找餐厅 | 顾客(你)点击餐厅招牌 | 桌面(Launcher)通知系统要启动APP |
2. 厨房准备 | 经理安排厨师、买菜、开火 | 系统创建进程,加载APP基础代码 |
3. 打扫店面 | 清洁桌椅、摆餐具、开灯 | 初始化APP,准备好空白界面框架 |
4. 做第一道菜 | 厨师做招牌菜(比如炒饭) | 构建首页UI(测量/布局/绘制) |
5. 端菜上桌 | 服务员把炒饭放到你面前 | 屏幕显示界面,你可以点击操作了! |
3.3 App 崩溃(闪退),可能是什么原因导致的
App 崩溃(闪退)的本质是未捕获的异常导致进程强制终止。以下是常见原因分类整理,附排查方向(无安卓基础也能看懂):
一、代码缺陷(占比超60%)
原因 | 典型场景 | 排查线索 |
---|---|---|
1. 空指针异常 | 调用未初始化对象的方法 | NullPointerException (日志关键词) |
2. 数组越界 | 访问不存在的数组位置(如列表第100项) | IndexOutOfBoundsException |
3. 主线程阻塞 | 主线程执行网络请求/复杂计算 | ANR (Application Not Responding)弹窗 |
4. 内存泄漏 | 长期持有Activity引用(如静态变量) | 反复打开/关闭页面后崩溃 |
5. 类型转换错误 | 强行将文本转数字失败 | ClassCastException |
二、设备资源问题
原因 | 触发条件 | 用户端现象 |
---|---|---|
1. 内存不足(OOM) | 加载大图/多图未优化 | 在图片列表页频繁崩溃 |
2. 存储空间不足 | 缓存写入失败 | 保存文件/更新时崩溃 |
3. CPU过载 | 死循环/复杂算法 | 手机发烫后闪退 |
4. 网络不稳定 | 未处理超时/弱网情况 | 切换4G/WiFi时崩溃 |
三、兼容性问题(安卓开发痛点)
问题类型 | 案例 | 解决方案 |
---|---|---|
1. 系统版本差异 | Android 6.0+ 动态权限未申请 | 在旧手机正常,新手机闪退 |
2. 厂商定制ROM | 小米后台限制/华为电池优化 | 仅特定品牌手机崩溃 |
3. 屏幕适配缺陷 | 小屏手机布局错乱导致点击异常 | 折叠屏/平板正常,小屏机崩溃 |
4. 架构差异 | 未提供arm64-v8a库 | 新手机(如骁龙8系)安装后闪退 |
四、外部依赖故障
依赖项 | 崩溃表现 | 快速验证法 |
---|---|---|
1. 第三方SDK冲突 | 接入新SDK后突然崩溃 | 注释SDK代码观察是否恢复 |
2. API请求异常 | 服务器返回非预期数据格式 | 用Postman模拟异常响应测试 |
3. 本地数据库损坏 | 升级后读取旧数据库失败 | 清除App数据后恢复正常 |
4. 权限被拒绝 | 未申请存储权限却写入文件 | Android 6.0+ 需运行时申请 |
五、特殊场景崩溃
场景 | 原因 | 调试技巧 |
---|---|---|
1. 低电量模式 | 后台服务被系统强制停止 | 电量<15%时复现崩溃 |
2. 多进程通信失败 | 跨进程调用超时未处理 | 绑定Service的代码加try-catch |
3. 配置变更 | 旋转屏幕导致Activity重建 | 查看onSaveInstanceState 逻辑 |
4. 多线程竞争 | 多个线程同时修改同一数据 | 加同步锁synchronized |
3.4 NOR的原因
在安卓系统中,NOR(Not Only Response) 通常指 ANR(Application Not Responding)的变种或衍生场景,即应用未响应的特殊类型。以下是深度解析的五大核心原因及解决方案:
一、主线程阻塞(80% NOR的元凶)
阻塞操作 | 后果 | 代码反例 |
---|---|---|
主线程网络请求 | 弱网时卡死界面 | new Thread(() -> http.get()).run() ❌ |
复杂数据计算 | JSON解析/大数据排序卡死 | Collections.sort(10万条数据) ❌ |
同步I/O操作 | 读写大文件/SQLite查询无响应 | File.read(100MB文件) ❌ |
二、跨进程死锁(IPC僵局)
三、内存枯竭连锁反应
内存不足 → NOR 的恶性循环
-
低内存时系统频繁 GC(垃圾回收)
-
GC 暂停所有线程(包括主线程)
-
主线程卡顿超过 5秒 → 触发 NOR
-
常见诱因:
-
内存泄漏(如静态持有Activity)
-
加载未压缩的巨图(1920x1080 → 8MB内存)
-
四、系统服务响应超时
安卓核心服务调用卡死
服务类型 | 超时阈值 | 高危操作 |
---|---|---|
ContentProvider | 10秒 | 跨进程查询未索引的百万行数据 |
BroadcastReceiver | 10秒 | onReceive()内复杂计算 |
Service | 20秒 | 后台服务初始化第三方SDK |
五、界面渲染雪崩
UI线程的隐形杀手
-
过度绘制
布局嵌套10层 → 测量/布局耗时激增 -
无效重绘
TextView.setText()
每秒调用60次 -
同步屏障失效
自定义View的onDraw()
包含循环