详解Android单元测试最佳实践

目的

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

简介

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

本地jvm的单元测试

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

Instrumentation测试

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

准备

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

App运行效果

依赖库

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

配置build.gradle

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

1

2

3

4

5

testOptions {

 unitTests {

  includeAndroidResources true

 }

}

添加测试依赖库

1

2

3

4

5

6

7

8

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需要模拟的静态类型

1

2

3

4

@RunWith(RobolectricTestRunner.class)

@Config(sdk = 21, constants = BuildConfig.class)

@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})

@PrepareForTest({PresenterFactory.class})

1

2

3

4

5

@Before

public void setUp() {

 appContext = RuntimeEnvironment.application.getApplicationContext();

 PowerMockito.mockStatic(PresenterFactory.class);

}

onCreate用例

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

1

2

3

4

5

6

@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消息

1

2

3

4

5

6

7

@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方法

1

2

3

4

5

6

7

8

9

10

11

12

13

@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需要模拟的静态类型

1

2

@RunWith(PowerMockRunner.class)

@PrepareForTest({ServiceFactory.class})

1

2

3

4

@Before

public void setUp() {

 PowerMockito.mockStatic(ServiceFactory.class);

}

成功路径用例

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@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());

}

失败路径用例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@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测试

1

2

3

4

5

6

7

8

9

10

public class SlowServiceImplTest {

 @Test

 public void fetch_data() {

  SlowServiceImpl impl = new SlowServiceImpl();

  String data = impl.fetch();

  assertEquals("from slow service", data);

 }

}

自动化测试

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

1

gradlew :app:testDebugUnitTest

总结

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

​现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/209532.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mmdetection测试保存到新的文件夹,无需标签

这个是用demo这个代码测试的&#xff0c;需要先训练一个pth文件夹&#xff0c;训练之后再调用pth文件夹进行测试。测试的代码文件名是&#xff1a;image_demo_new.py&#xff0c;代码如系所示&#xff1a; # Copyright (c) OpenMMLab. All rights reserved. import asyncio fr…

使用selenium的edge浏览器登录某为

互联网上基本都是某哥的用法&#xff0c;其实edge和某哥的用法是一样的就有一下参数不一样。 一、运行环境 Python&#xff1a;3.7 Selenium&#xff1a;4.11.2 Edge&#xff1a;版本 120.0.2210.61 (正式版本) (64 位) 二、执行代码 from time import sleepfrom selenium…

调新浪分享

前端写一个按钮,通过按钮来调出新浪界面, window.location.href http://service.weibo.com/share/share.php?url 这行代码调出新浪分享界面,要是想要添加一些图片和文字 使用: window.location.href http://service.weibo.com/share/share.php?url encodeURIComponent…

P2 Qt Creator创建第一个Qt程序

前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《LLinux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ &#x1f33a;本篇简介 &#xff1a;这一章我们学…

二百一十一、Flume——Flume实时采集Linux中的Hive日志写入到HDFS中(亲测、附截图)

一、目的 为了实现用Flume实时采集Hive的操作日志到HDFS中&#xff0c;于是进行了一场实验 二、前期准备 &#xff08;一&#xff09;安装好Hadoop、Hive、Flume等工具 &#xff08;二&#xff09;查看Hive的日志在Linux系统中的文件路径 [roothurys23 conf]# find / -name…

smarty模版 [BJDCTF2020]The mystery of ip 1

打开题目 点击flag给了我们一个ip 点击hint&#xff0c;查看源代码处告诉了我们要利用这个ip bp抓包&#xff0c;并添加X-Forward-For头 所以这道题是XFF可控 本来联想到XFF漏洞引起的sql注入&#xff0c;但是我们无论输入什么都会正常回显&#xff0c;就联想到ssti注入 我们…

CloudCompare 二次开发(23)——计算两点云之间的放缩倍数

目录 一、概述二、代码集成三、结果展示一、概述 使用CloudCompare编程实现计算两点云之间的放缩倍数。具体计算原理见:。 二、代码集成 1、mainwindow.h文件public中添加: void doActionComputeScale(); // 计算两点云的放缩倍数2、mainwindow.cpp文件void MainWin…

Python网络爬虫的基础理解-对应的自我理解误区

##通过一个中国大学大学排名爬虫的示例进行基础性理解 以软科中国最好大学排名为分析对象&#xff0c;基于requests库和bs4库编写爬虫程序&#xff0c;对2015年至2019年间的中国大学排名数据进行爬取&#xff1a;&#xff08;1&#xff09;按照排名先后顺序输出不同年份的前10…

Linux下通过find找文件---通过修改时间查找(-mtime)

通过man手册查找和-mtime选项相关的内容 man find | grep -A 3 mtime # 这里简单介绍了 -mtime &#xff0c;还有一个简单的示例-mtime n Files data was last modified n*24 hours ago. See the comments for -atime to understand how rounding affects the interpretati…

【已解决】解决Win7虚拟机打开网页报错的情况

因为刚才下载了个虚拟机&#xff0c;同样出现了无法安装VMtools的情况&#xff0c;所以想直接通过虚拟机的浏览器来下载一个补丁&#xff08;因为自己的U盘在虚拟机上面无法识别&#xff0c;应该是太老了Win7&#xff09; 结果发现Win7内置的IE浏览器太拉了。于是向下载一个火…

深度学习记录--神经网络表示及其向量化

神经网络表示 如下图 就这个神经网络图来说&#xff0c;它有三层&#xff0c;分别是输入层(Input layer)&#xff0c;隐藏层(Hidden layer)&#xff0c;输出层(Output layer) 对于其他的神经网络&#xff0c;隐藏层可以有很多层 一般来说&#xff0c;不把输入层算作一个标准…

UML图的各种类型以及软件设计师考试考察的方式

UML建模 前言 常见的UML的类型 UML 比前两题是更难的&#xff08;略高&#xff0c;但是学会就可以了。前两题是&#xff1a;数据流图&#xff0c;数据库的设计&#xff09;&#xff0c;因为UML图有很多类型&#xff1a;用例图&#xff0c;类图与对象图&#xff0c;顺序图&…

3_CSS层叠样式表基础

第3章-CSS层叠样式表基础 学习目标(Objective) 掌握标签选择器的使用掌握类选择器的使用了解id选择器和通配符选择器掌握font属性和color属性的应用 1.HTML的局限性 如果要改变下高度或者变一个颜色&#xff0c;就需要大量重复操作 总结&#xff1a; HTML满足不了设计者的需…

Emacs之dired模式重新绑定键值v(一百三十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

uniapp实战 —— 轮播图【数字下标】(含组件封装,点击图片放大全屏预览)

组件封装 src\components\SUI_Swiper2.vue <script setup lang"ts"> import { ref } from vue const props defineProps({config: Object, })const activeIndex ref(0) const change: UniHelper.SwiperOnChange (e) > {activeIndex.value e.detail.cur…

数据可视化:解锁企业经营的智慧之道

在现代企业管理中&#xff0c;数据可视化已经成为了一项重要的工具。它不仅仅是简单地展示数据&#xff0c;更是提供了深入理解数据、做出更明智决策的方法。作为一名可视化设计从业人员&#xff0c;我经手过一些企业自用的数据可视化项目&#xff0c;今天就来和大家聊聊数据可…

数字化升级,智慧医疗新时代——医院陪诊服务的技术创新

在信息技术飞速发展的今天&#xff0c;医疗服务正迎来数字化升级的新时代。本文将探讨如何通过先进技术的应用&#xff0c;为医院陪诊服务注入更多智慧元素&#xff0c;提升患者和家属的医疗体验。 1. 创新医疗预约系统 # Python代码演示医疗预约系统的简单实现 class Medic…

输入框的透明度影响placeholder的透明度怎么解决

有一个需求是需要写如上图所示的输入框。 首先想到的是调整输入的透明度 <div class"inputDiv"><img src"./images/search.png" /><input type"text" class"myInput" placeholder"请输入标题关键字"/> &…

飞天使-linux操作的一些技巧与知识点

命令行光标移动到行首行尾 ctrl a 跳到首 ctrl e 跳到尾/etc/passwd rpm 包格式 RPM&#xff08;Red Hat Package Manager&#xff09;是一种常用的Linux软件包管理系统&#xff0c;它使用特定的命名规则来标识和命名软件包。RPM包的名称格式通常遵循以下规则&#xff1a;…

FPGA时序分析与约束(0)——目录与传送门

一、简介 关于时序分析和约束的学习似乎是学习FPGA的一道分水岭&#xff0c;似乎只有理解了时序约束才能算是真正入门了FPGA&#xff0c;对于FPGA从业者或者未来想要从事FPGA开发的工程师来说&#xff0c;时序约束可以说是一道躲不过去的坎&#xff0c;所以这个系列我们会详细介…