testing-samples 系列实战:BasicUnitAndroidTest 设备端单元测试(Instrumented Unit Test)完整指南

发布时间:2026/10/14 3:15:46

testing-samples 系列实战:BasicUnitAndroidTest 设备端单元测试(Instrumented Unit Test)完整指南 示例工程【免费下载链接】testing-samplesA collection of samples demonstrating different frameworks and techniques for automated testing项目地址https://gitcode.com/gh_mirrors/te/testing-samples点击查看免费下载本篇技术指南围绕 testing-samples 仓库中的 unit/BasicUnitAndroidTest 示例展开讲解在设备或模拟器上运行的单元测试Android Unit Test也称 Instrumented Unit Test的完整实践它为何必须依赖 Android Framework、与本地单元测试及 UI 测试有何本质区别、如何在 Android Studio 与命令行中配置并运行以及如何解读测试报告。读完本文你将掌握以Parcelable为代表的需要真实 Android 运行时支撑的类的测试方法并能直接复用在 Android 项目中。什么是 Android Unit TestInstrumented Unit Test官方仓库的 README 对这一概念给出了非常精确的定义AnUnit Android Testis a test that needs an Android device or emulator but its different from a UI test because it doesnt start any activities.也就是说Android Unit Test 是一种需要 Android 设备或模拟器才能运行的测试但它与 UI 测试如 Espresso 测试不同——它不会启动任何 Activity。它运行在设备上的androidTest测试进程里因此可以访问 Android Framework 提供的真实 API但又不像 UI 测试那样关注界面交互与用户操作路径。三者对比可以这样理解测试类型运行位置是否需要设备是否启动 Activity典型用途本地单元测试JVM Test开发机 JVM否否纯逻辑、工具类、无 Android 依赖的代码Android Unit Test本示例设备/模拟器是否依赖 Android Framework 的类如Parcelable、SharedPreferencesUI / Instrumentation 测试设备/模拟器是是界面交互、端到端流程本示例恰好落在中间这一格被测对象是实现了Parcelable接口的模型类测试需要真实的android.os.Parcel机制但测试本身不启动任何界面。为什么这个测试无法脱离 Android 设备运行这是本示例最核心的看点。README 中明确指出In this sample the test cant run without the Android Framework because the Parcel class is used in one of the methods of the Parcelable interface and the way data is written into a Parcel and read from it is not trivial enough to be stubbed.被测类 LogHistory.java 是一个实现了Parcelable的日志模型内部用ListPairString, Long保存文本 时间戳形式的日志条目。其writeToParcel方法LogHistory.java 第 42-58 行先将数据拆成String[]与long[]两个数组依次写入ParcelOverride public void writeToParcel(Parcel out, int flags) { // Prepare an array of strings and an array of timestamps. String[] texts new String[mData.size()]; long[] timestamps new long[mData.size()]; // Store the data in the arrays. for (int i 0; i mData.size(); i) { texts[i] mData.get(i).first; timestamps[i] mData.get(i).second; } // Write the size of the arrays first. out.writeInt(texts.length); // Write the two arrays in a specific order. out.writeStringArray(texts); out.writeLongArray(timestamps); }对应的反序列化逻辑则位于私有构造函数LogHistory.java 第 91-114 行由CREATOR字段的createFromParcel触发先读入数组长度再按写入顺序依次读取字符串数组与长整型数组最后校验两个数组长度一致否则抛出IllegalStateException。这里的关键点在于Parcel是 Android 系统进程间通信IPC与状态保存机制的底层载体它涉及 Binder 驱动的序列化协议、栈式写入顺序、数据位置游标dataPosition等一套复杂的真实运行时行为。正如 README 所强调的Parcel的写入与读取方式trivial enough to be stubbed的反面——它并不平凡到可以用 Mock 简单替换。因此这个测试必须运行在真实的 Android 设备或模拟器上而不能像纯 JVM 单元测试那样在开发机本地直接执行。这正是把它放进androidTest目录而非test目录的根本原因Note that the unit test is placed in/androidTest/instead of/test/.项目结构被测类、测试类与界面演示本示例采用 Gradle 构建模块结构清晰unit/BasicUnitAndroidTest/ ├── app/ │ ├── build.gradle │ └── src/ │ ├── androidTest/java/.../LogHistoryAndroidUnitTest.java # 设备端单元测试 │ └── main/ │ ├── java/.../LogHistory.java # 被测类Parcelable 模型 │ ├── java/.../MainActivity.java # 界面演示展示 Parcelable 实战 │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── settings.gradle └── gradlew / gradlew.batREADME 中建议按以下顺序阅读这三个类LogHistory.java —— 被测类class under test实现ParcelableLogHistoryAndroidUnitTest.java —— Android 单元测试本体MainActivity.java —— 在真实界面中展示Parcelable的用法但请注意测试本身不会在设备或模拟器上启动任何 Activity。MainActivity是一个很好的实际用途佐证它允许用户向多行日志追加条目并在设备旋转时通过onSaveInstanceStateMainActivity.java 第 88-91 行把mLogHistory以outState.putParcelable(KEY_HISTORY_DATA, mLogHistory)的方式保存再在onCreate中通过savedInstanceState.getParcelable(KEY_HISTORY_DATA)恢复。这正是Parcelable序列化机制在 Android 生命周期中的经典应用场景——LogHistory的单元测试保证了这一保存/恢复链路的数据完整性。构建配置要点模块级构建文件 app/build.gradle 中有几处与本示例直接相关的配置defaultConfig { applicationId com.example.android.testing.unittesting.basicunitandroidtest minSdkVersion 14 targetSdkVersion 33 versionCode 1 versionName 1.0 testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner } dependencies { // Testing-only dependencies androidTestImplementation androidx.test:core: rootProject.coreVersion; androidTestImplementation androidx.test.ext:junit: rootProject.extJUnitVersion; androidTestImplementation androidx.test:runner: rootProject.runnerVersion; androidTestImplementation com.google.truth:truth: rootProject.truthVersion }testInstrumentationRunner显式声明了测试运行器为androidx.test.runner.AndroidJUnitRunner这是设备端测试能够通过connectedCheck被调度的前提测试相关依赖全部使用androidTestImplementation作用域表明它们只参与androidTest源集即src/androidTest/的编译与运行不会被打进应用 APK 的主包具体版本号集中在根构建文件 build.gradle 的ext块中统一管理coreVersion 1.5.0、extJUnitVersion 1.1.4、runnerVersion 1.5.0、truthVersion 1.1.3Android Gradle Plugin 版本为8.1.1。在 Android Studio 中配置并运行测试README 给出了在 IDE 中运行的完整六步流程下载项目代码推荐使用git clone方式获取 testing-samples 仓库在 Android Studio 中选择File|Open...指向项目的./build.gradle文件打开工程阅读相关源码LogHistory.java是被测类实现ParcelableLogHistoryAndroidUnitTest是 Android 单元测试MainActivity.java展示Parcelable的实际用法——但再次强调测试运行过程中不会在设备或模拟器上显示任何 Activity创建自定义运行器的测试配置运行器指定为androidx.test.runner.AndroidJUnitRunner打开Run菜单 |Edit Configurations新增一个Android Tests配置选择一个模块本示例中即app添加Specific instrumentation runnerandroidx.test.runner.AndroidJUnitRunner连接真机或启动模拟器运行新建的测试配置。配置完成后单元测试将自动执行无需任何手动干预。需要说明的是第 4 步的手动配置在常规工程中其实已被app/build.gradle里的testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner声明覆盖IDE 配置主要是在需要临时指定不同运行器或测试筛选条件时使用两步配置的目标是一致的。在命令行中使用 Gradle 运行不依赖 IDE 时使用 Gradle Wrapper 在命令行运行是更贴近 CI 的方式。README 给出的命令非常简洁./gradlew connectedCheck其中connectedCheck是 Android Gradle Plugin 提供的聚合任务它会连接所有已检测到的设备/模拟器执行androidTest源集中的所有测试等价于connectedAndroidTest及其变体Windows 环境请使用gradlew.bat替代./gradlew如果所有测试均通过终端会输出BUILD SUCCESSFUL消息任务结束码为 0适合直接接入持续集成流水线。测试用例到底在验证什么既然命令行会直接跑测试不妨深入看一下 LogHistoryAndroidUnitTest.java 中唯一的测试方法logHistory_ParcelableWriteRead第 48-68 行RunWith(AndroidJUnit4.class) SmallTest public class LogHistoryAndroidUnitTest { public static final String TEST_STRING This is a string; public static final long TEST_LONG 12345678L; private LogHistory mLogHistory; Before public void createLogHistory() { mLogHistory new LogHistory(); } Test public void logHistory_ParcelableWriteRead() { // Set up the Parcelable object to send and receive. mLogHistory.addEntry(TEST_STRING, TEST_LONG); // Write the data Parcel parcel Parcel.obtain(); mLogHistory.writeToParcel(parcel, mLogHistory.describeContents()); // After youre done with writing, you need to reset the parcel for reading. parcel.setDataPosition(0); // Read the data LogHistory createdFromParcel LogHistory.CREATOR.createFromParcel(parcel); ListPairString, Long createdFromParcelData createdFromParcel.getData(); // Verify that the received data is correct. assertThat(createdFromParcelData.size()).isEqualTo(1); assertThat(createdFromParcelData.get(0).first).isEqualTo(TEST_STRING); assertThat(createdFromParcelData.get(0).second).isEqualTo(TEST_LONG); } }这条测试链路完整模拟了序列化 → 反序列化 → 数据比对的闭环其中三个细节值得注意Parcel.obtain()与setDataPosition(0)obtain()从全局池中取出一块可复用的Parcel缓冲区写入完成后必须调用setDataPosition(0)把读取游标重置回起点否则读取会从缓冲区末尾开始直接导致数据错乱。这正是 README 所说写入与读取并不 trivial的直观体现走CREATOR.createFromParcel而非直接调用私有构造函数反序列化走的是与 Android 系统一致的公开入口验证的是真实的Parcelable.Creator契约断言采用 Google Truth 风格assertThat(...).isEqualTo(...)可读性更高测试类同时标注了RunWith(AndroidJUnit4.class)与SmallTest——前者指定 AndroidX 的 JUnit4 运行器后者声明这是一个小规模快速、无副作用的测试便于按规模分拣执行。查看测试报告命令行跑完./gradlew connectedCheck后HTML 格式的测试报告会自动生成在app/build/outputs/reports/androidTests/connected该目录下通常包含index.html总览页面及各测试类的详细结果页面会列出每个测试方法如logHistory_ParcelableWriteRead的执行状态、耗时与失败堆栈可以直接用浏览器打开查看也可以由 CI 系统作为构建产物归档。由于这是相对app模块的输出目录对应的仓库根路径为 app/build/outputs/reports/androidTests/connected该目录在构建后才会生成。小结什么时候应该选择这种测试形态从本示例可以提炼出一条清晰的选型判断当被测代码强依赖 Android Framework 的真实实现、且该实现难以用 Mock/Stub 替代时典型如Parcelable、Parcel、SharedPreferences、Context等就应当把单元测试放进src/androidTest/作为 Android Unit Test 在设备或模拟器上运行。它既保留了单元测试不启动 Activity、专注单一逻辑单元的轻量特性又借助 Instrumentation 环境获得了真实系统服务的支撑。实践时只需记住三条主线目录放对androidTest而非test、运行器配好AndroidJUnitRunner、跑法选好Android Studio 的 Android Tests 配置或./gradlew connectedCheck。在此基础上你就可以像本示例验证LogHistory的 Parcelable 契约一样为自己的模型类、存储层与系统服务交互代码建立可靠的设备端单元测试防线。赞分享示例工程【免费下载链接】testing-samplesA collection of samples demonstrating different frameworks and techniques for automated testing项目地址https://gitcode.com/gh_mirrors/te/testing-samples点击查看免费下载相关推荐OpenSSL 单元测试实战指南基于 cmocka 与 --wrap 的隔离测试体系test/unitOpenSSL 单元测试实战指南基于 cmocka 与 wrap 的隔离测试体系test/unit OpenSSL 作为一个承载 TLS 与密码学核心逻辑密码学网络安全通信rust-by-example 单元测试实战从 [test] 到 cargo test 的完整指南rust by example 单元测试实战从 test 到 cargo test 的完整指南 单元测试是 Rust 保证代码正确性的第一道防线。本篇基于 r文档教程Umbraco Backoffice 前端测试体系实战指南从 web/test-runner 单元测试到 Playwright E2E 的完整测试栈Umbraco Backoffice 前端测试体系实战指南从 web/test runner 单元测试到 Playwright E2E 的完整测试栈 本文是CMS后端上一篇opencodex 桌面端上下文窗口与推理强度控制Cycle 2 A-门审计合成实战解析下一篇Convex Backend 自托管到自有服务器Docker 部署、域名路由与 CLI 接入完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/14 3:15:46

生产环境Agent编排:以失败兜底为核心的设计实践

先说结论:把“不失败”当成生产环境里 Agent 编排的目标,是我见过最多项目翻车的根源。我自己做过的模拟项目X,一个面向终端用户的客服问答 Agent,从第一版开始就没指望大模型、第三方接口、甚至是编排器自身能一直稳定。每个环节…

2026/10/14 3:10:45

Hive行级更新实战:ACID事务与ORC分桶表配置指南

1. 从一次数据修正需求说起:为什么Hive的行级更新是个真问题做数据仓库的人大概率都遇到过这种场景:上游业务系统在凌晨批量同步数据时,某几条订单的状态字段写错了,或者某个用户的手机号录入了脏数据。放在MySQL里,一…

2026/10/14 4:15:49

工业物联网落地难?老设备联网的四大核心问题与实战路径

1. 工业现场的真实困境:为什么“联网”不是加个Wi-Fi那么简单我第一次走进某制造企业车间时,看到的不是想象中的全自动流水线,而是一排排沉默的PLC柜、贴着胶带的手写巡检表、还有工程师蹲在设备旁用万用表测电压的背影。当时产线负责人指着一…

2026/10/14 4:15:49

RecyclerView缓存复用机制全解析:五级缓存与调优实战

做Android开发这些年,RecyclerView的复用与缓存机制是我见过被误解最多、也最值得彻底搞明白的一套系统。面试必问,线上列表一旦卡顿又必背锅。网上讲这套机制的文章不少,但大多数要么只画了几张抽象流程图,要么只贴了源码注释&am…

2026/10/14 4:15:49

RESTful与GraphQL在企业系统对接中的选型与落地实践

做API集成的这些年,接触RESTful和GraphQL的机会都不少。头几年几乎所有外部系统对接都是RESTful,URI描述资源、HTTP动词表达动作、状态码告诉你对没对;后来GraphQL进入视野,前端团队欢呼“想查什么查什么”,但后端团队…

2026/10/14 4:15:49

Linux系统管理命令详解:从top到journalctl的运维实战

运维这行干久了,你会发现一个规律——真正拉开差距的不是你会不会部署某个服务、能不能写出漂亮的脚本,而是当你面对一台状况不明的服务器时,能不能用最基础的命令快速定位问题。我刚带团队那会儿,很多新人问我“系统管理命令”到…

2026/10/14 4:15:49

软考备份考点:完全/增量/差异备份的恢复链路与答题技巧

备考软考的朋友,特别是冲着信息系统项目管理师、系统架构设计师去的,大概率都背过一句话:完全备份是把整个数据集完整复制一份,增量备份只备份上次备份后发生变化的数据,差异备份只备份上次完全备份后发生变化的数据。…

2026/10/14 4:10:49

C#网络调试助手实战:Socket通信、异步收发与粘包拆包避坑指南

简介:C#网络调试助手是一款面向C#开发人员与网络调试工程师的实用工具,覆盖TCP/IP、UDP、socket及串口通讯等常见调试场景,集成了多协议收发能力,并支持将调试结果直接写入MySQL数据库,适合用于通信程序联调、报文分析…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑