mcpField
U
understanding-the-testing-pyramid

understanding-the-testing-pyramid

github.com/skydoves测试与质量33960 次安装22 周前更新
代码仓库

关于 understanding-the-testing-pyramid

面向 Android 测试设计者的技能指南,介绍 small、medium、big 三层测试范围,并与五层划分方式区分。它说明测试范围与本地或设备执行是不同维度,也澄清测试金字塔的定性建议及 70/20/10 比例的出处。

主要功能

  1. 01说明 small、medium、big 三层划分
  2. 02区分测试范围与执行位置
  3. 03对比三层与五层框架
  4. 04澄清测试金字塔的定性建议
  5. 05说明 70/20/10 的出处

适用场景

  1. 01为 Android 项目规划测试组合
  2. 02讨论本地测试与设备测试的分类
  3. 03评估测试套件过慢或测试范围混用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/skydoves/android-testing-skills --skill understanding-the-testing-pyramid

安装后在 Claude Code 中输入/understanding-the-testing-pyramid确认已加载。

详细介绍

理解测试金字塔——选择 3 层框架并坚持使用

当团队为每种行为都编写一个大型插桩测试,却不为底层逻辑编写单元测试时,Android 测试套件会拖慢 CI,并逐渐失去维护。Google 的 /training/testing/fundamentals 页面用三个范围——小型、中型、大型——来划分测试规模,/strategies 页面则在此基础上勾勒出一个定性金字塔。本技能采用 3 层框架,介绍另一种 5 层框架,避免作者将两者混为一谈,并如实说明广为流传的数字比例是从哪里来的。后续技能(../choosing-what-to-test/SKILL.md、../../strategies/applying-testing-strategies/SKILL.md)都以这套术语为基础。

何时使用本技能

  • 用户询问“我应该编写多少个单元测试和 UI 测试”,或者引用 70/20/10 / 80/15/5 的比例并询问 Google 文档中是否有这样的说法。
  • 用户把所有测试都称为“集成测试”或“端到端测试”,而 Agent 需要一套术语来对测试进行分类。
  • 用户反映“CI 很慢”或“测试套件要跑 40 分钟”,并想知道是否应该删除插桩测试。
  • 用户询问 Robolectric 算不算“单元测试”或“集成测试”——在 Google 的框架中,它属于本地中型测试。
  • 用户正在启动一个新模块,并询问从哪里开始(回答:根据 /strategies,先编写小型测试)。

何时不使用本技能

  • 用户想知道每种测试应该覆盖什么(哪些屏幕、哪些 ViewModel)——使用 ../choosing-what-to-test/SKILL.md。
  • 用户想配置 testImplementation 与 androidTestImplementation 这两个 Gradle 配置——使用 ../../strategies/organizing-test-source-sets/SKILL.md。
  • 用户想在 Fake / Mock / Stub / Spy 之间做选择——使用 ../../doubles/picking-test-doubles/SKILL.md。
  • 用户正在调试单个不稳定测试并想修复它——从 ../../../compose/synchronization/synchronizing-with-idle/SKILL.md 或 ../../../jvm-tests/coroutines/testing-coroutines-with-runtest/SKILL.md 开始。

先决条件

  • 一个至少有一个测试类的模块,以便讨论更具体。
  • 熟悉 src/test/(本地 JVM)与 src/androidTest/(插桩测试)的划分。如果不熟悉,请先阅读 ../../strategies/organizing-test-source-sets/SKILL.md。
  • 理解“范围”(小型/中型/大型)与“执行位置”(本地/插桩)是 Google 定义的两个独立维度。

两种框架(每项技能 / 每份设计文档中只能选一种)

Google 在相邻页面上发布了两种框架。两者并不矛盾,只是看问题的角度不同。

框架 A——小型 / 中型 / 大型(3 层)← 本技能推荐使用此框架

引自 /training/testing/fundamentals(“Android 中的测试类型” → “按范围划分”):

“测试也会按照范围进行分类,即测试覆盖的代码量。测试范围分为三类:

  • 单元测试或小型测试仅验证应用中一小部分代码,例如一个方法或类。
  • 端到端测试或大型测试同时验证应用的较大部分,例如整个屏幕或用户流程。
  • 中型测试介于两者之间,用于检查两个或更多单元之间的集成。” — developer.android.com/training/testing/fundamentals
范围覆盖内容典型运行器典型执行位置
小型一个方法或类JUnit4 + Mockito/MockK + fakesrc/test/(JVM)
中型集成的 2 个或更多类,可能通过 Robolectric 使用 Android 框架JUnit4 + Robolectric 或进程内 Androidsrc/test/(宿主机)或 src/androidTest/(插桩测试)
大型整个屏幕、用户流程或发布构建冒烟测试AndroidJUnitRunner + Espresso/Compose-test/UiAutomatorsrc/androidTest/
框架 B — 单元 / 组件 / 功能 / 应用 / 发布候选版本(5 层)

来自 /training/testing/fundamentals/strategies(“可扩展策略”表):

级别范围网络
单元依赖最少的单个方法或类无
组件模块或组件无
功能多个组件 / 模块“支持模拟网络访问”
应用整个应用,可调试的二进制文件不适用
发布候选版本整个发布构建,已压缩不适用

大多数团队采用 3 层的框架 A,因为这种形式更简单、引用也更多。只有在设计多团队 CI 策略,且更精细的划分确实有用时,才采用框架 B。绝不能在同一份文档中混用二者——选定一种并贯彻到底。

范围与执行位置是正交的

这是页面上最重要的一段引文,也是开发者说“我的所有测试都是插桩测试”时 Agent 应引用的内容:

“并非所有单元测试都在本地运行,也并非所有端到端测试都在设备上运行。例如:

  • 大型本地测试:你可以使用在本地运行的 Android 模拟器,例如 Robolectric。
  • 小型插桩测试:你可以验证代码是否能与某项框架功能(例如 SQLite 数据库)良好协作。” — developer.android.com/training/testing/fundamentals

两个维度:

                 LOCAL (JVM)              INSTRUMENTED (device)
SMALL    pure JUnit + fakes          unit test of SQLite via real DB
MEDIUM   Robolectric component      component test on real Android
BIG      Robolectric flow           Espresso / Compose UI flow

Robolectric 是一个本地 Android 模拟器。它不是“插桩测试”——它在 src/test/ 下通过 JVM 运行。请参阅 ../../strategies/organizing-test-source-sets/SKILL.md 和跨类别技能 ../../../jvm-tests/robolectric/using-robolectric-correctly/SKILL.md。

定性金字塔

/strategies 页面只确定了定性的形态:

“大多数应用都应包含许多小型测试和相对较少的大型测试,形成金字塔形。” — developer.android.com/training/testing/fundamentals/strategies

“**要点:**一般来说,应尽量在开发周期的早期添加测试。这通常意味着从小型测试开始。” — developer.android.com/training/testing/fundamentals/strategies

这就是 Google 当前关于数量的全部说法:“小型测试多,大型测试少”。没有给出比例。

70/20/10 比例的真正出处

“70% 单元测试、20% 集成测试、10% 端到端测试”这一规则广为引用,但在当前的 /training/testing/fundamentals 或 /training/testing/fundamentals/strategies 页面上并不存在(截至 2026-05-06;已在 tasks/research/R8-android-fundamentals.md 中核实)。这个数字来自 Google 自己的工程文献——Software Engineering at Google 第 11 章——而非 Android 培训页面。

绝不能将“70/20/10”归因于 developer.android.com。这个数字比例是 Software Engineering at Google 中广为流传的社区经验法则(https://abseil.io/resources/swe-book,第 11 章)——转述时务必谨慎,除非逐字核对过原文,否则不要将任何具体句子作为直接引文呈现。这本书将这种划分视为经验法则,而非定律:不同领域适用的比例各不相同;请参阅下文“何时打破金字塔”。

成本与速度的权衡

金字塔底部较宽的原因:

层级每项测试的实际耗时失败信号维护成本
小型1-50 ms精确定位到某个类或方法低
中型(Robolectric)50-500 ms精确定位到某个组件中
大型(插桩测试)5-30 s只会说“屏幕坏了”高(不稳定、模拟器状态、动画)

扁平或倒置的金字塔(大量大型测试、少量小型测试)会导致:

  • CI 变慢(比覆盖率相当、以单元测试为主的测试套件慢 10 到 100 倍)。
  • 不稳定率高;不稳定性与测试规模和外部依赖相关(/strategies 指出,单元测试层和组件测试层“无网络访问”)。
  • 失败信号模糊——“X 屏幕坏了”,而不是“函数 Y 对输入 Z 返回了错误值”。
  • 重构痛苦——UI 测试会断言渲染出的文本,文案每次变更都会导致测试失败。

/training/testing/instrumented-tests 明确说明了代价:

“我们建议仅在必须针对真实设备的行为进行测试时使用插桩测试。” — developer.android.com/training/testing/instrumented-tests

这就是 Google 用自己的话表达的“将大型测试作为最后手段”。

有意打破金字塔的情况

金字塔是默认做法,而不是铁律。以下情况可以打破它:

  1. 产品本身就是集成。 一个主要负责连接三个供应商 SDK 的支付 SDK,在功能或应用测试中获得的价值高于对胶水代码进行单元测试。单元测试只会对模拟对象进行同义反复式的验证。
  2. 逻辑由框架负责。 一个纯 CRUD 屏幕,使用由 DataStore 支持的 ViewModel 和标准 Material 组件,几乎没有什么需要进行单元测试。大型测试可以发现连接问题;小型测试则只是在测试 runBlocking { dataStore.data.first() }。
  3. 确定性是重点。 渲染正确性测试套件(截图测试、动画计时)属于大型测试层,因为缺陷会在那里显现。参见跨类别技能 ../../../compose/audit/auditing-compose-test-suite/SKILL.md。
  4. 成本效益发生逆转。 在 Firebase Test Lab / Gradle Managed Devices 上,大型测试的每分钟成本可能低到维护成本反而占主导——而单元测试在稳定性上仍然更胜一筹。

以下理由不足以证明打破金字塔是合理的:

  • “单元测试太难搭建了”——这说明架构不佳,而不是堆积大型测试的理由。参见 developer.android.com/training/testing/fundamentals(“采用可测试的应用架构……”)。
  • “我们无法模拟 X”——用伪实现代替模拟对象。参见 ../../doubles/picking-test-doubles/SKILL.md。
  • “它在我的机器上能运行,多加些设备测试直接发布吧”——不稳定性会蔓延到整个测试套件。使用 ../../../adb/observability/extracting-logs-with-logcat/SKILL.md 进行诊断。

模式

模式:错误与正确——引用 70/20/10 比例

> Per Google's developer documentation, the testing pyramid recommends
> a 70/20/10 split between unit, integration, and end-to-end tests.
> Source: developer.android.com/training/testing/fundamentals/strategies

错误原因:截至 2026-05-06,策略页面上并没有这个比例。页面只定性地说“多做小型测试,少做大型测试”。此处引用 developer.android.com 属于捏造。


> Google's Android docs commit to a qualitative pyramid only:
> "Most apps should have many small tests and relatively few big tests"
> (developer.android.com/training/testing/fundamentals/strategies).
> The familiar 70/20/10 split comes from Software Engineering at Google,
> ch. 11 (https://abseil.io/resources/swe-book), not the Android docs.
模式:错误与正确——对 Robolectric 进行分类

> Robolectric tests are instrumented tests because they exercise the
> Android framework. Put them in src/androidTest/.

错误原因:Robolectric 在 JVM 上运行,不需要 Android 设备或模拟器。根据 /training/testing/fundamentals(“大型本地测试:可以使用本地运行的 Android 模拟器,例如 Robolectric”),无论范围如何,Robolectric 都属于 src/test/ 中的本地测试。


> Robolectric is a local (JVM) Android simulator. By scope it can be small,
> medium, or big depending on what the test exercises. Place it in src/test/.
> Use src/androidTest/ only for tests that require a real device.
模式:错误与正确——在一份设计文档中使用金字塔表述

> Our suite has 200 small tests, 40 medium tests, 8 feature tests, and
> 3 release-candidate tests, which is a healthy pyramid.

错误原因:这混淆了表述方式 A(“小型/中型”)和表述方式 B(“功能/候选发布版”)。读者无法判断这里的“小型”是指表述方式 A 中的小型,还是表述方式 B 中的单元测试。


> Our suite has 200 small tests, 40 medium (Robolectric component) tests,
> and 11 big (instrumented Compose-test + Espresso) tests. Per Google's
> small/medium/big framing on /training/testing/fundamentals, the ratio
> is roughly 80/16/4, in line with the pyramid recommendation.

决策矩阵——新测试应该采用什么范围?

Question                                         → Scope
----------------------------------------------------------
"Does it depend on the Android framework?"
  No                                             → Small (src/test/)
  Yes, simulated via Robolectric                 → Medium (src/test/)
  Yes, must be a real device                     → Big (src/androidTest/)
"Does it exercise more than one class?"
  No                                             → Small
  Yes, but no I/O / no UI                        → Medium
  Yes, with UI rendering or system services      → Big
"Does it cross a network or process boundary?"
  No                                             → Small or Medium
  Yes (mocked)                                   → Medium (Feature in Framing B)
  Yes (real)                                     → Big (Application/RC in Framing B)

强制规则

  • 默认技能文案和设计文档必须使用“小 / 中 / 大”三层框架。只有在明确使用 /strategies 页面时,才采用“Unit / Component / Feature / Application / Release Candidate”五层框架。
  • 不得在同一份文档中混用这两种框架。择一使用。
  • 不得将“70/20/10”这一数值比例归因于 developer.android.com。请引用 Software Engineering at Google(https://abseil.io/resources/swe-book)。
  • 不得将 Robolectric 描述为“instrumented test”。它是一种本地 Android 模拟器,可以承载任何范围的测试。
  • 每个新测试必须通过注释或命名约定标注其范围(使用 androidx.test.filters 中的 @SmallTest、@MediumTest、@LargeTest),以便 CI 进行分片。
  • **建议:**按照 Google 的“从小型测试开始”指南,新模块优先从小型测试开始;只有小型测试无法覆盖时,才添加中型 / 大型测试。
  • **建议:**CI 运行缓慢时,先删除冗余的大型测试,再添加更多小型测试——通过削减顶部来收紧测试金字塔,而不只是拓宽底部。

验证

  • 模块中的每个测试类都可通过 @SmallTest、@MediumTest、@LargeTest 之一(或通过源集位置)访问。
  • src/androidTest/ 中没有测试可以在不降低保真度的情况下,改为在 src/test/ 中作为 Robolectric 主机测试运行。(如果可以,请移动它。)
  • CI 日志报告的每次变更中各层的单元测试墙钟时间都小于 instrumented 测试墙钟时间。
  • 没有设计文档将“70/20/10”归因于 developer.android.com。
  • 没有设计文档混用框架 A 和框架 B 的术语。
  • 团队的测试策略文档在术语表中只定义一种框架。

参考资料

  • developer.android.com/training/testing/fundamentals — 小 / 中 / 大范围、“并非所有单元测试都在本地运行”引文、可测试架构的理由。
  • developer.android.com/training/testing/fundamentals/strategies — 定性金字塔(“大量小型测试,少量大型测试”)、五级 Unit / Component / Feature / Application / Release Candidate 表格、“从小型测试开始”指南。
  • developer.android.com/training/testing/instrumented-tests — “仅在必须针对真实设备的行为进行测试时使用 instrumented tests”。
  • Software Engineering at Google,第 11 章 — 70/20/10 比例的出处。https://abseil.io/resources/swe-book
  • tasks/research/R8-android-fundamentals.md — 逐字引文、版本敏感说明、“页面上没有 70/20/10”的发现。
  • androidx.test.filters — 用于运行时分片的 @SmallTest / @MediumTest / @LargeTest 注解。
  • 同级技能:../choosing-what-to-test/SKILL.md、../../doubles/picking-test-doubles/SKILL.md、../../strategies/applying-testing-strategies/SKILL.md、../../strategies/organizing-test-source-sets/SKILL.md。
  • 跨类别:../../../jvm-tests/robolectric/using-robolectric-correctly/SKILL.md、../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md、../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md、../../../compose/audit/auditing-compose-test-suite/SKILL.md。

评论

支持 Markdown