跳转至

测试与代码质量

面试追问地图

主问题 必讲关键点 下一层追问
测试分层 范围、速度、真实性 测试金字塔比例如何确定
单元测试 AAA、独立、可重复 私有方法是否测试、静态依赖
Mock 隔离依赖、验证行为 过度 Mock、Fake 的价值
Spring 测试 切片测试、Context 成本 Context 缓存、事务回滚
Testcontainers 真实依赖、环境一致 启动速度、数据隔离
覆盖率 执行覆盖而非质量证明 分支覆盖、变异测试
并发测试 同步起点、不变量、重复运行 不稳定测试如何治理
契约测试 消费者/提供者约定 与集成测试、E2E 的区别
Code Review 风险、边界、可运维性 如何控制评审规模和效率
代码质量 圈复杂度、技术债、可测性 重构节奏、遗留系统治理

测试题应结合一个真实缺陷说明“这个测试为什么能在上线前发现问题”。


一、测试分层

单元测试、集成测试、端到端测试的区别?

难度 🟡

类型 范围 特点
单元测试 单个类或方法 快、隔离、定位准确
集成测试 多组件、数据库、中间件 验证真实协作,执行较慢
端到端测试 完整业务链路 最接近用户,但成本高且脆弱

测试金字塔强调底层测试数量多、上层测试数量少,但比例应结合系统类型调整。

什么样的代码容易测试?

  • 依赖通过构造器注入。
  • 业务逻辑与 IO、副作用分离。
  • 方法职责单一。
  • 时间、随机数、外部服务可替换。
  • 避免全局状态和静态依赖。

难测试通常是设计耦合问题,不应全部依靠高级 Mock 技巧绕过。


二、JUnit 与断言

一个好的单元测试包含什么?

使用 Arrange-Act-Assert:

准备输入和依赖
  ↓
执行被测行为
  ↓
验证输出、状态或交互

测试名应描述场景和预期结果,例如:

shouldRejectOrderWhenStockIsInsufficient

参数化测试适合什么场景?

适合用多组输入验证同一规则:

  • 边界值。
  • 等价类。
  • 不同枚举或状态。
  • 格式解析。

参数化测试不能代替复杂场景的独立语义测试。

如何测试异常?

不仅验证异常类型,还应验证:

  • 错误码或关键消息。
  • 依赖是否未被调用。
  • 状态是否保持不变或正确回滚。

三、Mock

Mock、Stub、Fake 有什么区别?

  • Stub:返回预设数据。
  • Mock:除了返回数据,还验证交互。
  • Fake:可工作的简化实现,如内存仓库。

什么时候不应该 Mock?

  • 纯值对象和简单数据结构。
  • 被测类内部实现细节。
  • 所有对象都 Mock 导致测试只验证调用顺序。
  • 可以低成本使用真实组件的场景。

过度 Mock 会让重构频繁破坏测试,却无法发现真实集成问题。

如何验证外部调用?

只验证有业务意义的交互:

  • 是否发送了一次支付请求。
  • 是否在失败后没有发消息。
  • 是否携带正确幂等键。

不要对无关方法调用顺序做过度约束。


四、Spring 测试

@SpringBootTest 有什么特点?

它会启动完整或接近完整的 Spring Context,适合集成测试,但速度较慢。

应按需选择:

  • 纯业务类:普通 JUnit。
  • MVC 切片:@WebMvcTest。
  • JPA 切片:@DataJpaTest。
  • 完整集成:@SpringBootTest。

如何测试 Controller?

重点验证:

  • 参数绑定和校验。
  • HTTP 状态码。
  • 响应结构和错误码。
  • 鉴权和异常处理。

业务分支应主要在 Service 单元测试中覆盖,避免 Controller 测试过重。

如何测试数据库和中间件?

优先使用:

  • Testcontainers 启动真实数据库、Redis、Kafka 等。
  • 独立测试 Schema 和数据。
  • 每个测试可重复执行,避免依赖执行顺序。

H2 等内存数据库与 MySQL 方言、锁和执行计划可能不同,不能替代关键数据库集成测试。


五、测试策略

测试覆盖率越高越好吗?

覆盖率只能说明代码被执行过,不能证明断言有效。

重点关注:

  • 核心业务规则。
  • 边界和异常路径。
  • 历史故障回归。
  • 高风险变更。

覆盖率适合作为发现明显缺口的指标,不适合作为唯一质量目标。

如何测试并发代码?

  • 控制线程启动时机,避免依赖 sleep。
  • 使用 CountDownLatch、Barrier 等同步工具。
  • 重复运行增加暴露概率。
  • 验证最终状态和不变量。
  • 必要时使用专门并发测试工具。

并发测试通过不代表绝对正确,还需依赖设计证明、代码审查和线上指标。

如何测试时间相关逻辑?

注入 Clock,不要在业务逻辑中直接散落调用系统时间。

这样可以稳定测试过期、跨天、时区和夏令时场景。

什么是契约测试?

契约测试验证服务提供方和消费方对接口结构、字段和行为的约定,适合微服务独立发布。

它不能替代端到端测试,但能更早发现接口不兼容。


六、代码质量

Code Review 重点看什么?

  • 正确性和边界条件。
  • 并发、事务和一致性。
  • 安全和敏感数据。
  • 可读性和职责边界。
  • 测试是否覆盖风险。
  • 监控、日志和回滚能力。

格式问题应尽量交给自动化工具。

什么是圈复杂度?

圈复杂度反映代码中独立执行路径数量。复杂度过高通常意味着:

  • 分支过多。
  • 方法承担多个职责。
  • 测试组合爆炸。

治理方式包括提取策略、拆分方法和用状态机显式表达流程。

如何治理技术债?

  • 记录影响和风险,不只记录“代码不好看”。
  • 优先处理频繁变更、故障高发和阻碍交付的区域。
  • 将治理拆入正常迭代。
  • 用故障率、交付时间、复杂度等指标验证收益。

参考资料