Android 单元测试实战—— 调研与选型

发表于:2019-9-25 11:21

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Alex_MaHao    来源:掘金

  单元测试搞了一段时间,发现网上很多关于单元测试的文章都是讲了概念,讲了框架的使用,但对于一个实际的项目的操作,因为项目的复杂性,框架的稳定性等,确往往无法进行。本篇博客从实际出发,基于实际的项目总结而出。
  本系列文章不会涉及到单元测试的概念,以及它的各种现实意义。仅从实现入手,关于它的优劣不做分析。
  单元测试系列会分为三篇博客:
  Android单元测试调研与选型
  基于Powermock的Android单元测试常用方法指南
  基于Cobertra&sonarqube的单元测试覆盖率统计
  调研与选型
  Google官方文提供了单元测试的支持。在创建项目的时候会默认创建test和androidTest目录。分别是单元测试和集成测试。单元测试是对方法的测试,粒度较小,无需运行在真机上。集成测试需要每次运行需要跑在真机上,粒度较大,运行时间较长,而且不利于一些自动化的工作。
  本系列的核心从单元测试入手,以自动化为目的。
  单元测试一个绕不开的话题就是对于android.jar的问题。由于android.*的官方类,在运行单元测试的时候,只有方法的声明,内部的所有方法都会throw new RuntimeException('Stub')。那么一旦有调用官方类的地方,比如View,Intent,Activity等,就会报错,导致单元测试无法执行。
  一种常见的解决方式是通过架构来解决,将一些代码逻辑和官方类解耦,比如MVP,对Presenter做测试,因为大部分逻辑都在Presenter,所以还ok。
  但是不幸的,一般搞单元测试的时候,很难是从一个新项目入手的,架构很难变动,一旦修改架构,影响范围比较广。
  而我们的项目就是这样的,所有的逻辑都在Activity中编写,一旦测试逻辑就肯定绕不开官方类。
  官方文档上提到了单元测试的两种解决方案。
  一种是通过Mock来解决,及将官方类的调用方法给代理一下,不会实际的调用官方类的相关方法。
  另外一种是Robolectric,该框架通过在jvm上模拟android虚拟机,以单元测试的方式来完成集成测试。
  Robolectric(放弃)
  因为最终没有使用这个框架,所以先介绍一下这个框架。
  该框架相当于是搭建了一个android虚拟机,其运行单元测试的时候,实质是运行了一个app。那么其做测试的逻辑更倾向于appium等的UI测试,查询一个控件,模拟点击,验证逻辑。
  因为其模拟的虚拟机,那么他对官方的方法做了扩展,提供了一些列的ShadowXXX类,便于做验证和模拟。比如获取当前弹出的dialog,最后一个弹出的toast等等。
  调研的时候,该框架最新版本为4.3,并且从4.0开始,已经开始和官方的androidx.test下测试库进行兼容。可以通过官方espresso完成一些列操作。那么一套代码,技能在控制台运行,又能在模拟器上运行,想想还是挺美好的。
  但是 !
  因为我们的项目都是在Activity里面写的,一些业务逻辑都是使用私有方法,那么相对私有方法做验证,通过查询控件和通过UI的展示来验证逻辑的正确与否,十分的复杂。
  举个例子:
   @Override
  public void onClick(View v) {
  switch (v.getId()) {
  case R.id.skip:
  startNextActivity(false);
  break;
  case R.id.splash_image:
  startNextActivity(true);
  break;
  default:
  break;
  }
  }
  private synchronized void startNextActivity(boolean isClick) {}
  我想验证不同的View点击,调用的startNextActivity的参数是否正确。如果从UI上验证十分的复杂,而且万一startNextActivity的内部逻辑有问题,或者比较巧妙,那么UI的验证也不一定准确。
  有方法解决吗??肯定有,便是Mock,这个后面会说。
  能够Mock私有方法的常用的是Powermock,但是!!!!!
  该框架和Robolectric存在着各种兼容问题,在我头发掉了一地之后,也没有解决。
  所以放弃了!!!!注意是放弃了Robolectric!!!!
  Mock (使用)
  mock是单元测试中常用的一种方式,通过对即将调用方法的修改,模拟调用方法的返回值等等,具体的概念百度上一大堆。我就不再这里废话了。
  官方文档上建议使用Mockito完成mock操作,但是该库不支持静态方法,私有方法,final等的mock。
  上面提到了Powermock,他提供更加强大的mock功能,而且它提供了Mockito的支持,使用上和Mockito基本上一样。
  比如说,对于上面的代码,使用Powermock方法验证逻辑如下:
   @Test
  public void onClickSkip() throws Exception {
  // mock activity, activity的所有方法都不会被执行
  LauncherActivity activity = PowerMockito.mock(LauncherActivity.class);
  // 指定activity的onClick不被`mock,调用真实的逻辑,以便进行单元测试
  PowerMockito.doCallRealMethod().when(activity, "onClick", ArgumentMatchers.any(View.class));
  // Mock 官方类
  View view = PowerMockito.mock(View.class);
  // 指定getId的返回值
  PowerMockito.doReturn(R.id.skip).when(view, "getId");
  // 调用测试的方法
  activity.onClick(view);
  // 验证指定的方法和参数是否被滴啊用
  PowerMockito.verifyPrivate(activity).invoke("startNextActivity", false);
  }
  代码注释很清楚,不在废话。
  按照上面的思路,其实可以验证大部分的单元测试逻辑。
  总结
  综上所述,决定使用Powermock为基础,完成单元测试的编写。
  
      本文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号