ZhuanLan客户端单元测试入门从DateConvertTest到API Mock的完整指南【免费下载链接】ZhuanLan非官方知乎专栏 - Android项目地址: https://gitcode.com/gh_mirrors/zh/ZhuanLanZhuanLan是一款优秀的知乎专栏Android客户端为开发者提供了学习Android应用开发的绝佳案例。本文将带你从简单的DateConvertTest入手逐步掌握Android单元测试的核心技巧最终学会如何对网络API进行Mock测试构建健壮的移动应用测试体系。 ZhuanLan项目简介ZhuanLan是一个非官方的知乎专栏Android客户端它采用了Material Design设计风格提供了流畅的阅读体验。项目使用Retrofit进行网络请求Gson进行数据解析并实现了优雅的UI交互效果。项目核心模块网络层app/src/main/java/io/bxbxbai/zhuanlan/core/Api.java工具类app/src/main/java/io/bxbxbai/zhuanlan/utils/Utils.java数据模型app/src/main/java/io/bxbxbai/zhuanlan/bean/ 单元测试基础从DateConvertTest开始让我们从项目中的第一个测试用例开始学习。在ZhuanLan中有一个基础的单元测试类DateConvertTest它位于app/src/androidTest/java/io/bxbxbai/zhuanlan/io/bxbxbai/zhuanlan/test/DateConvertTest.java这个测试类继承自AndroidTestCase主要用于测试时间转换功能。让我们分析一下它的结构public class DateConvertTest extends AndroidTestCase { public void testDateConvert() { String str 2015-03-18T14:43:5008:00; StopWatch.log(Utils.convertPublishTime(str)); } }测试要点解析测试方法命名使用test前缀这是JUnit的命名约定测试对象测试Utils.convertPublishTime()方法输入数据使用ISO 8601格式的时间字符串验证方式通过日志输出验证结果 改进DateConvertTest添加断言原始的测试用例只输出了结果没有进行断言验证。让我们改进它添加更完整的测试逻辑public class DateConvertTest extends AndroidTestCase { public void testDateConvert() { // 测试数据 String testTime 2015-03-18T14:43:5008:00; // 执行转换 String result Utils.convertPublishTime(testTime); // 验证结果不为空 assertNotNull(转换结果不应为空, result); // 验证结果格式 assertTrue(结果应包含时间单位, result.contains(年前) || result.contains(月前) || result.contains(天前) || result.equals(今天)); } public void testWithinDays() { // 测试withinDays方法 String recentTime 2026-07-17T14:43:5008:00; String oldTime 2015-03-18T14:43:5008:00; // 验证近期时间在7天内 assertTrue(Utils.withinDays(recentTime, 7)); // 验证旧时间不在7天内 assertFalse(Utils.withinDays(oldTime, 7)); } } 理解Utils工具类的时间转换逻辑要编写有效的测试我们需要理解被测试的代码逻辑。让我们看看Utils.convertPublishTime()的实现app/src/main/java/io/bxbxbai/zhuanlan/utils/Utils.java#L35-L60这个方法的核心功能是将ISO 8601格式的时间字符串转换为相对时间描述如3天前、2月前等。它的转换逻辑基于以下时间单位时间单位秒数描述年31,536,000秒12个月 × 30天 × 24小时 × 3600秒月2,592,000秒30天 × 24小时 × 3600秒天86,400秒24小时 × 3600秒边界条件测试当天发布的内容 → 返回今天1-30天前的内容 → 返回N天前1-12月前的内容 → 返回N月前1年以上前的内容 → 返回N年前 搭建Android单元测试环境1. 项目结构配置ZhuanLan项目使用标准的Android项目结构app/ ├── src/ │ ├── main/ # 主代码 │ ├── androidTest/ # Android测试代码 │ └── test/ # 单元测试代码如有2. 依赖配置查看项目的build.gradle文件确保包含测试依赖dependencies { androidTestImplementation junit:junit:4.12 androidTestImplementation androidx.test:runner:1.4.0 androidTestImplementation androidx.test.espresso:espresso-core:3.4.0 }3. 测试运行配置在Android Studio中可以通过以下方式运行测试右键点击测试类 → Run DateConvertTest使用命令行./gradlew connectedAndroidTest API Mock测试模拟网络请求在实际开发中网络请求是最需要测试的部分。ZhuanLan使用Retrofit进行API调用让我们学习如何对这些API进行Mock测试。1. 理解API接口首先查看API接口定义 app/src/main/java/io/bxbxbai/zhuanlan/core/Api.javapublic interface Api { GET(/api/columns/{id}/posts) CallListPost getPosts(Path(id) String id, Query(limit) int limit, Query(offset) int offset); GET(/api/columns/{id}) CallUser getUser(Path(id) String id); }2. 创建Mock API测试我们可以使用Mockito库来模拟API响应public class ApiMockTest extends AndroidTestCase { private Api mockApi; Override protected void setUp() throws Exception { super.setUp(); // 创建Mock对象 mockApi Mockito.mock(Api.class); } public void testGetPostsSuccess() { // 准备模拟数据 ListPost mockPosts new ArrayList(); Post post new Post(); post.setTitle(测试文章标题); post.setPublishedTime(2026-07-18T10:00:0008:00); mockPosts.add(post); // 创建模拟的Call对象 CallListPost mockCall Mockito.mock(Call.class); ResponseListPost successResponse Response.success(mockPosts); // 设置Mock行为 Mockito.when(mockApi.getPosts(test-id, 10, 0)) .thenReturn(mockCall); Mockito.when(mockCall.execute()) .thenReturn(successResponse); // 执行测试 CallListPost call mockApi.getPosts(test-id, 10, 0); ResponseListPost response call.execute(); // 验证结果 assertTrue(response.isSuccessful()); assertEquals(1, response.body().size()); assertEquals(测试文章标题, response.body().get(0).getTitle()); } public void testGetPostsFailure() { CallListPost mockCall Mockito.mock(Call.class); Mockito.when(mockApi.getPosts(invalid-id, 10, 0)) .thenReturn(mockCall); Mockito.when(mockCall.execute()) .thenThrow(new IOException(网络错误)); // 验证异常处理 try { CallListPost call mockApi.getPosts(invalid-id, 10, 0); call.execute(); fail(应该抛出IOException); } catch (IOException e) { assertEquals(网络错误, e.getMessage()); } } }3. 使用MockWebServer进行集成测试对于更真实的网络测试可以使用OkHttp的MockWebServerpublic class ApiIntegrationTest extends AndroidTestCase { private MockWebServer server; private Api api; Override protected void setUp() throws Exception { super.setUp(); server new MockWebServer(); server.start(); Retrofit retrofit new Retrofit.Builder() .baseUrl(server.url(/).toString()) .addConverterFactory(GsonConverterFactory.create()) .build(); api retrofit.create(Api.class); } Override protected void tearDown() throws Exception { server.shutdown(); super.tearDown(); } public void testGetPostsWithMockServer() throws IOException { // 准备模拟响应 String jsonResponse [{\title\:\测试文章\,\publishedTime\:\2026-07-18T10:00:0008:00\}]; server.enqueue(new MockResponse() .setResponseCode(200) .setBody(jsonResponse) .addHeader(Content-Type, application/json)); // 执行请求 CallListPost call api.getPosts(test-column, 10, 0); ResponseListPost response call.execute(); // 验证请求 RecordedRequest request server.takeRequest(); assertEquals(/api/columns/test-column/posts?limit10offset0, request.getPath()); // 验证响应 assertTrue(response.isSuccessful()); assertEquals(1, response.body().size()); assertEquals(测试文章, response.body().get(0).getTitle()); } } 测试最佳实践1. 测试金字塔原则遵循测试金字塔原则构建健康的测试体系╱╲ ╱ ╲ E2E测试 (少量) ╱ ╲ ╱______╲ ╱ ╲ 集成测试 (适量) ╱__________╲ ╱ ╲ 单元测试 (大量) ╱______________╲2. 测试命名规范使用清晰的测试命名让测试意图一目了然test[方法名]_[场景]_[预期结果]testConvertPublishTime_RecentDate_ReturnsTodaytestGetPosts_NetworkError_ThrowsException3. 测试数据管理创建测试数据工厂避免硬编码public class TestDataFactory { public static Post createPost(String title, String time) { Post post new Post(); post.setTitle(title); post.setPublishedTime(time); post.setAuthor(createAuthor(测试作者)); return post; } public static Author createAuthor(String name) { Author author new Author(); author.setName(name); author.setAvatar(new Avatar()); return author; } }4. 测试覆盖率目标为ZhuanLan项目设定合理的测试覆盖率目标模块类型建议覆盖率优先级工具类(Utils)90%高业务逻辑80%高UI组件70%中数据模型60%低️ 常见问题与解决方案问题1Android依赖问题症状测试运行时找不到Android类解决使用androidTest目录而非test目录或使用Robolectric模拟Android环境问题2网络请求测试慢症状网络测试执行时间长解决使用MockWebServer或Mockito模拟网络层避免真实网络请求问题3异步测试困难症状异步回调难以测试解决使用CountDownLatch或Espresso的IdlingResourcepublic void testAsyncOperation() throws InterruptedException { final CountDownLatch latch new CountDownLatch(1); final ListString results new ArrayList(); api.getPosts(test, 10, 0).enqueue(new CallbackListPost() { Override public void onResponse(CallListPost call, ResponseListPost response) { results.add(success); latch.countDown(); } Override public void onFailure(CallListPost call, Throwable t) { results.add(failure); latch.countDown(); } }); // 等待异步操作完成 assertTrue(latch.await(5, TimeUnit.SECONDS)); assertEquals(success, results.get(0)); } 总结与下一步通过本文的学习你已经掌握了基础单元测试从DateConvertTest入手理解Android测试框架断言技巧使用合适的断言验证测试结果API Mock测试使用Mockito和MockWebServer模拟网络请求测试最佳实践遵循测试金字塔编写可维护的测试代码下一步学习建议探索Espresso进行UI测试学习使用Robolectric进行快速单元测试研究持续集成中的测试自动化实践测试驱动开发(TDD)方法论ZhuanLan项目为你提供了绝佳的Android开发学习案例通过系统地学习单元测试你不仅能提高代码质量还能深入理解Android应用架构。现在就开始为你的ZhuanLan客户端添加更多测试用例吧【免费下载链接】ZhuanLan非官方知乎专栏 - Android项目地址: https://gitcode.com/gh_mirrors/zh/ZhuanLan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考