详解Android单元测试最佳实践

目的

充分的单元测试就是提高代码质量最有效的手段之一,而单元测试严重依赖代码的可测试性,本文主要通过一个简单的DEMO演示如何对Android原生应用进行单元测试,同时示例代码采用MVP模式以提高代码的可读性和可测试性

简介

在Android原生应用开发中,存在两种单元测试:本地JVM测试和Instrumentation测试。本文仅介绍本地JVM测试

本地jvm的单元测试

这种方式运行速度快,对运行环境没有特殊要求,可以很方便的做自动化测试,是单元测试首选的方法

Instrumentation测试

Instrumentation测试需要运行在Android环境下,可以是模拟器或者手机等真实设备。这种方式运行速度慢,且严重依赖Android运行环境,更适合用来做集成测试

准备

我准备了一个简单的APP,模拟一个耗时的网络请求获得一段数据并显示在界面上,针对这个APP编写单元测试用例并进行本地单元测试。

App运行效果

依赖库

依赖库 作用
JUnit-4.12 基础得单元测试框架
Robolectric-3.8 Android SDK测试框架
PowerMock-1.6.6 模拟被测对象依赖的静态方法
Mockito-1.10.19 模拟被测对象依赖的对象

配置build.gradle

增加编译选项,在测试中包含资源文件

 testOptions {
  unitTests {
   includeAndroidResources true
  }
 }

添加测试依赖库

 testImplementation 'junit:junit:4.12'
 testImplementation 'org.robolectric:robolectric:3.8'
 testImplementation 'org.robolectric:shadows-supportv4:3.8'
 testImplementation 'org.powermock:powermock-module-junit4:1.6.6'
 testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.6'
 testImplementation 'org.powermock:powermock-api-mockito:1.6.6'
 testImplementation 'org.powermock:powermock-classloading-xstream:1.6.6'
 testImplementation 'org.mockito:mockito-all:1.10.19'

测试Activity

测试Activity主要是测试它各个生命周期的状态变化、对外界输入的响应是否符合预期,Activity测试完全依赖Android SDK,需要用Robolectric。

Robolectric是一个开源的单元测试框架,能够完全模拟Android SDK并在JVM中运行。

UI依赖于Persenter,在Activity中通过静态工厂方法创建依赖的Presenter实例,需要使用PowerMock来模拟创建Presenter过程,完成Presenter模拟对象的注入

配置

  • 通过@RunWith指定使用RobolectricTestRunner
  • 通过@Config配置Robolectric的运行环境
  • 通过@PrepareForTest配置PowerMock需要模拟的静态类型
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 21, constants = BuildConfig.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({PresenterFactory.class})
 @Before
 public void setUp() {
  appContext = RuntimeEnvironment.application.getApplicationContext();
  PowerMockito.mockStatic(PresenterFactory.class);
 }

onCreate用例

通过Robolectric的ActivityController来构建并管理activity的生命周期,运行至onCreate阶段,然后验证这个阶段text1是否正确初始化

 @Test
 public void onCreate_text1() {
  MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get();
  String expect = appContext.getString(R.string.hell_world);
  assertEquals(expect, ((TextView)activity.findViewById(R.id.lbl_text1)).getText());
 }

Click Button1用例

Activity完全显示以后,验证button1的click操作是否显示toast消息

 @Test
 public void btn1_click() {
  MainActivity activity = Robolectric.setupActivity(MainActivity.class);
  activity.findViewById(R.id.btn_1).performClick();
  String expect = appContext.getString(R.string.hell_world);
  assertEquals(expect, ShadowToast.getTextOfLatestToast());
 }

Click Button2用例

Activity完全显示以后,验证button2的click操作是否调用了presenter的fetch方法

 @Test
 public void btn2_click() {
  MainContract.Presenter presenter = Mockito.mock(MainContract.Presenter.class);
  PowerMockito.when(PresenterFactory.create(Mockito.any(MainContract.View.class), Mockito.any(AppExecutors.class)))
    .thenReturn(presenter);

  MainActivity activity = Robolectric.setupActivity(MainActivity.class);

  activity.findViewById(R.id.btn_2).performClick();

  Mockito.verify(presenter, Mockito.times(1))
    .fetch();
 }

测试Presenter

Presenter的测试一般可以不用依赖Android SDK了,Presenter依赖于底层的领域服务,也依赖上层View,demo中对领域服务的依赖没有通过构造函数的方式注入,而是通过静态工厂方法构建,还是需要用到PowerMock

配置

  1. 通过@RunWith指定使用PowerMockRunner
  2. 通过@PrepareForTest配置PowerMock需要模拟的静态类型
@RunWith(PowerMockRunner.class)
@PrepareForTest({ServiceFactory.class})
 @Before
 public void setUp() {
  PowerMockito.mockStatic(ServiceFactory.class);
 }

成功路径用例

验证View的方法是否成功调用且调用参数是否一致

 @Test
 public void fetch_success() {
  String expected = "hello world";
  SlowService service = Mockito.mock(SlowService.class);
  Mockito.when(service.fetch()).thenReturn(expected);
  PowerMockito.when(ServiceFactory.create())
    .thenReturn(service);

  MainContract.View view = Mockito.mock(MainContract.View.class);
  MainPresenter presenter = new MainPresenter(view, executors);

  presenter.fetch();

  Mockito.verify(service, Mockito.times(1)).fetch();
  Mockito.verify(view, Mockito.times(1)).onFetchStarted();
  Mockito.verify(view, Mockito.times(1)).onFetchCompleted();
  Mockito.verify(view, Mockito.times(0)).onFetchFailed(Mockito.anyObject());
  ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
  Mockito.verify(view, Mockito.times(1)).onFetchSuccess(captor.capture());
  assertEquals(expected, captor.getValue());
 }

失败路径用例

 @Test
 public void fetch_failed() {
  RuntimeException exception = new RuntimeException("fetch failed");

  SlowService service = Mockito.mock(SlowService.class);
  Mockito.when(service.fetch()).thenThrow(exception);
  PowerMockito.when(ServiceFactory.create())
    .thenReturn(service);

  MainContract.View view = Mockito.mock(MainContract.View.class);
  MainPresenter presenter = new MainPresenter(view, executors);

  presenter.fetch();

  Mockito.verify(service, Mockito.times(1)).fetch();
  Mockito.verify(view, Mockito.times(1)).onFetchStarted();
  Mockito.verify(view, Mockito.times(1)).onFetchCompleted();
  ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  Mockito.verify(view, Mockito.times(1)).onFetchFailed(captor.capture());
  assertEquals(exception, captor.getValue());
  Mockito.verify(view, Mockito.times(0)).onFetchSuccess(Mockito.anyString());
 }

测试Service

Service不会对上层有依赖,可以直接使用JUnit测试

public class SlowServiceImplTest {

 @Test
 public void fetch_data() {
  SlowServiceImpl impl = new SlowServiceImpl();
  String data = impl.fetch();
  assertEquals("from slow service", data);
 }

}

自动化测试

自动化测试一般是在持续集成环境中使用命令来执行单元测试

gradlew :app:testDebugUnitTest

总结

写完这个demo,总觉得给Android APP做单元测试还是非常简单的,作为一个优秀的程序员,怎么能够不关注自己的代码质量呢,还是自己动手试试吧

源码下载

https://github.com/hziee514/android-testing

参考资料

Robolectric
Using PowerMock
Mockito

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android测试中Appium的一些错误解决技巧

    问题 1. error: Failed to start an Appium session, err was: Error: Requested a new session but one was in progress 之前的会话没有关闭,然后你又运行了测试实例,也没有设置覆盖. 解决: 1. 重新停止appium服务,开启Appium服务 2. 在Genarel Setting那里设置覆盖Session,重启Appium 测试结束在AfterClass加driver.quit() 2. e

  • Android利用Espresso进行UI自动化测试的方法详解

    为什么需要UI自动化测试? 我有一个观点,对于重复的工作,那么程序都是可以代替的,我想这是作为一个程序员的一个基本素养(能偷懒的绝不干活).UI自动化测试就是为了应付一些重复的工作,比如说测试某个功能,那么从应用点击,再经过一系列的点击页面才能到达这个页面,然后进行测试,那么我们是不是可以写段代码让app自动跑起来,自动来到那个界面进行测试呢?答案是肯定的,这就是本文所要说的自动化测试. 引言 谷歌2013年的时候开源了espress,谷歌的思路是,等到它足够成熟和稳定以后,将其迁移到Andro

  • 详解appium+python 启动一个app步骤

    询问度娘搭好appium和python环境,开启移动app自动化的探索(基于Android),首先来记录下如何启动待测的app吧! 如何启动APP?1.获取包名:2.获取launcherActivity.获取这两个关键东西的方法很多,推荐使用sdk自带的aapt:aapt即Android Asset Packaging Tool,在SDK的build-tools目录下.该工具可以查看apk包名和launcherActivity,当然还有更多的功能,有兴趣的可以查看相关资料. 一.下载aapt:

  • Android单元测试之对Activity的测试示例

    上一篇文章已经介绍了单元测试的作用和简单示例,如果不了解的读者可以先阅读上一篇Android单元测试-作用以及简单示例. 这篇文章主要介绍常见的Activity中的测试. 对Acitivity的测试 对于Activity,我们大致有两种测试需求: 1.在Activity正常启动后,查看界面布局是否正确,包括View的点击事件等是否正确. 2.需要在Activity启动前完成各种数据的部署,然后查看Activity的效果. 对于这两种需求,笔者分别做了两个示例解说: 1.检测一个布局中的butto

  • Android自动测试工具Monkey的实现方法

    1. Android Monkey 实现操作流程: 准备:在eclipse里安装Phyon插件,可以选择在线安装,也可以下载zip解压后放在eclipse安装目录的dropins下,如 : /personal/software/android_developtools/adt-bundle-mac-x86_64-20130522/eclipse/dropins/PyDev 2.8.2 插件准备就绪就重启eclipse,检验PyDev是否正常工作,然后开始MonkeyRunner测试: 第一步:

  • 浅谈Android单元测试的作用以及简单示例

    前提概要 受人嫌弃的单元测试 对于单元测试这个知识点,其实很多开发者是不太接触的,包括笔者,在实习之前也并未实用过单元测试,或者说并没感受到单元测试的好处. 对于bug的调试,笔者之前更倾向于使用log和断点调试,可以说会了这两个,大部分的逻辑bug都能自己解决了.这两个与看似臃肿的单元测试代码相比更受大家的喜爱. 但是,使用log和断点调试的前提是开发人员较少,甚至是单人开发的情况.如果我自己开发,我完全可以每次都使用集成测试,我知道每一个功能会涉及哪些模块的代码,然后根据逻辑设置log或者断

  • 简单谈谈android studio 的单元测试

    面对android studio Run 一次项目要等好几分钟的痛点,不得不研究一下android studio 的单元测试. 其实我的目的很简单,在不对视图进行操作的前提下,测试一些activity 的生命周期,或网络拉取数据的一些处理,比如解析 json 数据啊,做网络请求啊等等,也就是对 Model层的测试.这些不需要操作视图,但在没有单元测试环境下,比如我们网络请求一些数据,Log 打印看看是否请求成功,却又要 利用模拟器或真机Run 一次项目,花费好几分钟,这是不能容忍的. 于是乎,强

  • Android App开发的自动化测试框架UI Automator使用教程

    Android的自动化测试有很多框架,其中ui automator是google官方提供的黑盒UI相关的自动化测试工具,(GitHub主页:case使用java写,今天实践了一下官方文档中样例程序,其中还是有一些小问题需要总结一下的. 环境准备: 1.JDK(是的,你没看错,基础的android开发环境必备),以及对应的环境变量配置,不会的可以自己百度下下 2.Android Studio(IDE尊崇个人意愿) 3.android SDK以及配置 4.ANT(主要用于build我们的脚本,生成j

  • 在Android打包中区分测试和正式环境浅析

    前言 本文主要介绍了关于Android打包中区分测试和正式环境的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: APK打包正式环境和测试环境注意 最近在项目打包中,遇到一个问题,每次打包都要区分正式发布包和测试版本的包,主要是修改一下配置 public static final boolean EXTERNAL_RELEASE = true; 当变量为true的时候,就是正式包,反之就是测试包.每次上线的时候我就提醒自己要小心,千万不能出错了,然而终于湿脚了,一个渠道打

  • Python脚本在Appium库上对移动应用实现自动化测试

    采用Appium进行自动化的功能性测试最酷的一点是,你可以使用具有最适合你的测试工具的任何一门语言来写你的测试代码.大家选择最多的一个测试编程语言就是Python. 使用Appium和Python为iOS和Android应用编写测试代码非常容易. 在这篇博文中我们将详细讲解使用Appium下的Python编写的测试的例子代码对一个iOS的样例应用进行测试所涉及的各个步骤,而对Android应用进行测试所需的步骤与此非常类似. 开始,先自https://github.com/appium/appi

  • Android和iOS 测试五个最好的开源自动化工具

    自动化测试在产品测试上有着非常重要的作用.实现测试自动化有多种积极的方式,包括最大限度地减少测试执行时间:在关键的发布阶段,用更少的时间确保更大的覆盖范围:在产品开发阶段,可靠又重复性地运行以确保没有引进回归问题.最大限度地减少重复性回归测试循环时的人为错误和疏忽的风险. 在市场上有很多可用的工具,开源的或要付费的.虽然付费和开源工具各有利弊,但是后者在自动化测试社区得到了广泛的应用.根据项目需求来选择合适的自动化工具是非常棘手的. 下面介绍5个最佳的iOS和Android开源自动化工具. 1.

  • Ubuntu中为Android系统上实现内置C可执行程序测试Linux内核驱动程序

    在前一篇文章中,我们介绍了如何在Ubuntu上为Android系统编写Linux内核驱动程序.在这个名为hello的Linux内核驱动程序中,创建三个不同的文件节点来供用户空间访问,分别是传统的设备文件/dev/hello.proc系统文件/proc/hello和devfs系统属性文件/sys/class/hello/hello/val.进一步,还通过cat命令来直接访问/proc/hello和/sys/class/hello/hello/val文件来,以验证驱动程序的正确性.在这一篇文章里,我

  • Android 中构建快速可靠的 UI 测试

    前言 让我一起来看看 Iván Carballo和他的团队是如何使用Espresso, Mockito 和Dagger 2 编写250个UI测试,并且只花了三分钟就运行成功的. 在这篇文章中,我们会探索如何使用Mockito(译者注:Mockito是java编写的一个单元测试框架),Dagger 2 去创建快速可靠的Android UI测试.如果你正在开始编写Android中的UI 测试或者希望改善已有测试性能的开发者,那么这篇文章值得一读. 我第一次在安卓应用中使用UI自动化测试是在几年前使用

  • Android Monkey压力测试详细介绍

    Monkey 是Android SDK提供的一个命令行工具, 可以简单,方便地运行在任何版本的Android模拟器和实体设备上. Monkey会发送伪随机的用户事件流,适合对app做压力测试 阅读目录 环境搭建 什么是Monkey Monkey 用来做什么 Monkey程序介绍 Monkey 架构 Monkey弱点 Monkey 参数大全 Monkey 命令 基本参数介绍 Monkey 实例 Monkey 查看包名 Monkey  日志分析 必须重视Crash 环境搭建 安装Android SD

  • android开机自启动APP及使用adb命令测试方法

    android开机自动运行APP实现方式其实很简单.在android系统运行时,会发出"android.intent.action.BOOT_COMPLETED"这个系统广播,因此我们监听它,并进行打开APP的操作即可.现在大多数的android手机系统都默认禁止第三方安装的APP开机自启动,只有系统APP(system/app)才默认有这个权限,所以一般都需要去安全中心或者手机管家中去设置为允许.这里说个题外话,手机软件除非特殊情况,不然最好不要做开机自启动,开机自启动一般适用于an

随机推荐