分享基于安卓项目的单元测试总结

news2024/10/7 16:26:45

前言:

负责公司的单元测试体系的搭建,大约有一两个月的时间了,从最初的框架的调研,到中期全员的培训,以及后期对几十个项目单元测试的引入和推进,也算是对安卓的单元测试有了一些初步的收获以及一些新的认知,因此写下这篇文章来进行一个记录和总结。

以下的所有内容纯属个人观点,欢迎讨论。

一.单元测试标准

1.测试维度

单元测试有很多维度,比如针对功能点的维度,或者针对方法的维度。那么我们的项目,该如何定义这个维度呢?

安卓源码的项目中,是以功能点的维度来写单元测试的,比如验证发送一个广播的功能,验证就是广播接收者是否收到通知。这其中的流程,包含广播发送到系统侧,系统侧接收和处理,系统侧通知应用,应用分发给接收者等四个步骤。对于安卓系统来说,发送广播后,其一定能保证接收到广播,但是这样单元测试就存在耦合度,因为如果系统侧代码有问题的话,整个流程是跑不通的。对于安卓的项目,很多点我们是不能使用原生的对象,而是需要使用Mock的对象,但是随着项目的耦合度增加,环节的增多,需要mock的对象和验证点会爆炸性的增长,导致后期的单元测试方法的成本会几何式增长。

当然,以功能点为维度的话,也会有其好的一方面,如果单元测试不通过,则代表着流程中必有一环出现了问题,更容易暴露出问题。

如果以方法为维度,则不会存在依赖和耦合的问题,但是覆盖的范围则会小很多。所以到底应该以功能点为维度,还是以方法为维度呢?

我认为,这个最终还是取决于项目的结构和形式。如果是UI级别的项目,项目复杂度相对较轻,或者使用了各种框架完成了视图绑定,这种项目,自然适合针对功能点的维度来写单元测试。但是如果项目耦合度比较高,复杂度较高的话,则应该选择基于方法的维度,对耦合的部分使用mock对象进行切割。

2.覆盖范围

功能点:核心功能点

首先,单元测试应该覆盖所有的核心功能点。验证一个方法的时候,并不是方法中所有的点都需要验证,就比如Activity中onCreate方法中的某些内容,比如针对onCreate写单元测试的时候,验证initView/initListener/init三个方法是否被执行其实并没有意义,我们应该写单元测试代码分别对这三个方法进行验证,这才是我们的业务逻辑点。

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      initView();
      initListener();
      init();
  }

另外,我们应该验证具体的实现逻辑方法,对于一些中转方法,也不应该验证,比如ActivityThread中的ApplicationThread类就不应该被验证。部分ApplicationThread中的代码参考:

private class ApplicationThread extends IApplicationThread.Stub {
      public final void scheduleReceiver(Intent intent, ActivityInfo info,
          CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
          boolean sync, int sendingUser, int processState) {
          updateProcessState(processState, false);
          ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                  sync, false, mAppThread.asBinder(), sendingUser);
          r.info = info;
          r.compatInfo = compatInfo;
          sendMessage(H.RECEIVER, r);
      }

      public final void scheduleCreateBackupAgent(ApplicationInfo app,
              CompatibilityInfo compatInfo, int backupMode, int userId, int operationType) {
          CreateBackupAgentData d = new CreateBackupAgentData();
          d.appInfo = app;
          d.compatInfo = compatInfo;
          d.backupMode = backupMode;
          d.userId = userId;
          d.operationType = operationType;

          sendMessage(H.CREATE_BACKUP_AGENT, d);
      }
      ...
  }

3.覆盖率要求

单元测试会有一个覆盖率,分别针对类,方法,行,大多数公司关注的是行代码覆盖率,并且会把其目标定在90%甚至95%的高目标。

个人感觉,至少对于安卓的项目,这样的高覆盖率其实是不可取的。比如上面的例子中,安卓源码中的单元测试类ActivityThreadTest中,对ApplicationThread中的内容就完全没有验证,因为这部分代码属于对输入数据的组装和逻辑的分发,并没有什么可执行的逻辑。从我们单元测试作用的角度上讲,其并符合任何一条作用,因此对于这种代码写单元测试也是没有意义的。

所以,单元测试应该覆盖的是我们的所有逻辑处理代码。如果是我来做的话,我会选择把ApplicationThread抽成单独的类,并且打上不需要单元测试的标记避免被统计在内。

在这种场景下,我认为行覆盖率的目标应该设定在85%。未能覆盖的部分,包含不方便抽成单独类的部分,常量定义的部分等等。

4.命名规范

单元测试也是写代码,写代码就应该有一定的规范。

参照安卓源码中的单元测试类,按照如下的方案来制定单元测试的命名规范更为合适。

1).单元测试类对应被测试类,在被测试类后面添加Test代表是对其的单元测试。

比如被测试类为ActivityA的话,则单元测试类的命名为ActivityATest。

2).如果是以方法为维度的单元测试类,则对应的测试方法命名为:test+测试方法。

比如被测试方法为methodA的话,则测试方法为testMethodA(驼峰命名)。

一个测试方法可以覆盖多个源方法,同样,一个源方法也可以拆分成多个测试方法分别验证。

3).如果是以功能点为维度的单元测试类,则对应的测试方法命名为:test+对应的功能点。

比如验证广播能否发送到接收者,则其方法名为:testResult。

二.单元测试的作用

1.单元测试并不能有效提高项目质量

了解到,有的公司用单元测试来替代集成测试甚至是黑盒测试,个人感觉是不可取的。也许,这些公司的单元测试的范围已经覆盖了部分的功能测试,但是单元测试终归是验证的方法级的功能点,如果强行的使其覆盖功能测试,会造成一些不好的效果。

单元测试保证的是一个方法内的输入输出项,当项目开发新需求或者重构的时候,单元测试可以帮助我们快速识别到对原有项目的影响点,这才是单元测试应保证的内容。

2.快速识别新功能的影响

这个很容易理解,如果新的改动影响到了方法中原有的逻辑,则老的单元测试是跑不通的。这时候我们就需要判断,是按照需求修改单元测试用例,还是新写的逻辑有问题了。

3.发现隐藏的问题

这也属于单元测试推行过程中意外的收获。进入到某个页面后,refreshData方法被调用了两次,但是由于两次调用的逻辑都是一致的,所以单看表现,确实不知道这个方法被调用了两次。

但是通过单元测试验证,验证方法执行此时是否为1,则验证出该方法被调用次数并不是1次,而从发现了这个多次调用的问题。

//被检测的类型
  public class MVPPresenter implements IMVPActivityContract.IMainActivityPresenter {
      //这个方法被调用了多次
      @Override
      public void requestInfo() {
          //请求数据,订阅,并显示
          Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;
          Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();
          Disposable disposable = observable
                  .subscribeOn(Schedulers.io())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(consumer);
      }
  }

  //单元测试类
  public class MVPActivityTest {
      @Test
      public void testInit() {
          ...
          MVPPresenter mockPresenter = mock(MVPPresenter.class);
          ...
          //验证requestInfo方法被调用的此时是否是1次
          verify(mockPresenter, times(1)).requestInfo();
      }
  }

4.督促我们解决项目中的耦合性

单元测试可以很好的衡量项目耦合度。负责公司项目单元测试体系搭建的时候,和很多项目的负责人进行沟通,有很多人表示,单元测试的case十分难写。排查下来,无一例外,全部都是耦合度太高的原因。他们把大量的功能,以及需要分开执行的环节耦合到一个方法里面。

比如BroadcastReceiver的onReceive方法中,不但执行参数的接收逻辑,还把后续的逻辑操作逻辑一并写在了onReceive方法中,甚至于有的还会在这里new一个线程去执行相关逻辑,这样的耦合度,单元测试方法必然是很难写的。反之,如果项目的耦合度很低,那么单元测试就会很好写。

5.对方法的客观评价

我们经常提到一个概念叫做圈复杂度,对于衡量方法内的圈复杂度,那么单元测试就是一个很好的指标。圈复杂度越高,单元测试代码中,其case就会越多。对于那种方法很短,但是验证case很多的,其圈复杂度往往就会很高。

所以,通过对于单元测试代码的阅读,就可以很容易的衡量出其圈复杂度。

6.注释的补充

有的文章把单元测试称之为最好的注释,因为它可以对一个方法的输入和输出进行一个直观的展示,会比方法的注释更大的详细。但是在我看来,注释和单元测试应该各有各的优势,对于一个方法来说,单元测试的介绍固然比注释介绍的更清晰,但是同时也增加了我们的阅读成本。所以我更愿意称之为是注释的一个补充。

如果只是为了reiview,或者对项目有一些初步的了解,那么注释就足够了。

但是如果阅读源代码是为了在其基础上进行进一步的改造,那么就必须对原有方法有一个深入的了解,因为单元测试就是一个很好的工具。

比如安卓源码中,对ContextImpl中对粘性广播sendStickyBroadcast的介绍有很多,但是反观其单元测试方法:

public void testSetSticky() throws Exception {
      Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
      intent.putExtra("test", LaunchpadActivity.DATA_1);
      ActivityManager.getService().unbroadcastIntent(null, intent,
              UserHandle.myUserId());

      ActivityManager.broadcastStickyIntent(intent, UserHandle.myUserId());
      addIntermediate("finished-broadcast");

      IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1);
      Intent sticky = getContext().registerReceiver(null, filter);
      assertNotNull("Sticky not found", sticky);
      assertEquals(LaunchpadActivity.DATA_1, sticky.getStringExtra("test"));
  }

通过单测代码,我们就可以清楚的知道,粘性广播是一种允许先发送,后注册也可以接收到的广播。 

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取  

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

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

相关文章

【雕爷学编程】Arduino动手做(131)---跑马灯矩阵键盘模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

线性代数基础--矩阵

矩阵 矩阵是由排列在矩形阵列中的数字或其他数学对象组成的表格结构。它由行和列组成&#xff0c;并且在数学和应用领域中广泛使用。 基本概念 元素&#xff1a;矩阵中的每个数字称为元素。元素可以是实数、复数或其他数学对象。 维度&#xff1a;矩阵的维度表示矩阵的行数和…

vtk创建点

使用vtk库创建三维空间中的点 引言开发环境示例一项目结构实现代码 运行效果示例二项目结构实现代码 运行效果总结 引言 本文仅适合初学者。 本文不提供vtk动态库的生成&#xff0c;以及在QtCreator中的引进vtk时的配置。 本文先由示例一开始&#xff0c;然后再在示例一的基础…

aws使用外部 ID对其他账号授权

点击前往授权,进入控制台 https://signin.aws.amazon.com/signin?redirect_urihttps%3A%2F%2Fconsole.aws.amazon.com%2Fconsole%2Fhome%3FhashArgs%3D%2523%26isauthcode%3Dtrue%26state%3DhashArgsFromTB_eu-north-1_f2d9c316b93c0026&client_idarn%3Aaws%3Asignin%3A%…

Glassdoor美国公司员工及面试者评价数据

一、数据简介 除了股东、债权人、政府等外部利益相关者外&#xff0c;员工的利益更应该得到公司的恰当保护&#xff0c;因为员工才是公司创造价值的真正主体。提高企业在产品市场的竞争力&#xff0c;首先就是要提高员工对企业的满意度&#xff0c;只有员工的满意度更高、幸福感…

7个技巧,助你同时轻松管理和跟踪多个项目

仅仅想到要兼顾这么多重要的职责&#xff0c;就会让许多专业的项目经理感到焦虑。当涉及多个项目的多种项目管理工具的处理&#xff0c;即使对于了解项目管理的项目经理来说&#xff0c;也是一项艰巨的任务&#xff0c;而对于在这个领域没有经过适当培训的人来说&#xff0c;这…

强化学习从基础到进阶--案例与实践[7.1]:深度确定性策略梯度DDPG算法、双延迟深度确定性策略梯度TD3算法详解项目实战

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

计算机网络—数据链路层

文章目录 数据链路层服务差错编码多路访问协议信道划分随机访问MAC协议 数据链路层服务 该层中的帧数据结构&#xff1a; 帧头部会因为不同的局域网协议而不同&#xff0c;因此会在另一篇博文中继续介绍不同的帧数据报&#xff0c;不在本博文介绍。&#xff08;不过除了PPP协…

Docker学习笔记11

Docker容器镜像&#xff1a; 1&#xff09;docker client 向docker daemon发起创建容器的请求&#xff1b; 2&#xff09;docker daemon查找本地有客户端需要的镜像&#xff1b; 3&#xff09;如无&#xff0c;docker daemon则到容器的镜像仓库中下载客户端需要的镜像&#…

线性代数基础--向量

目录 向量的概念 基本概念 抽象概念 向量的意义 几何意义 物理意义 欧式空间 特点和性质 行向量与列向量 行向量 列向量 两者的关系 向量的基本运算与范数 向量的基本运算 向量的加法 数乘运算&#xff08;实数与向量相乘&#xff09; 转置 向量的范数 向量…

echart 设置柱状图y轴最大刻度

start 最近接到需求希望柱状图 y轴最大高度可以略高一些&#xff1b;柱状图的数据能展示在柱状图的上方 记录一下相关配置项 解决方案 官方文档说明 https://echarts.apache.org/zh/option.html#xAxis.max 效果 代码 {key: business,title: {text: 业务领域分类,textSt…

DAY32:回溯算法(七)全排列+全排列Ⅱ(排列问题)

文章目录 46.全排列思路树形图used数组的作用 伪代码完整版时间复杂度总结 47.全排列Ⅱ思路树形图 完整版时间复杂度总结 46.全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xf…

C#和LABVIEW的对决:哪种上位机编程语言更适合你?

今天&#xff0c;我们将谈论主流的上位机编程语言。你听说过C#和LABVIEW吗&#xff1f;它们是的上位机编程语言&#xff0c;C#作为自动化主流编程语言特别受欢迎&#xff0c;LABVIEW用于自动化测试&#xff0c; 首先&#xff0c;我们来了解C#语言。C#是一种文本语言&#xff0c…

2023年江西省研究生数模竞赛植物的多样性

2023年江西省研究生数模竞赛 植物的多样性 原题再现 植物作为食物链中的生产者&#xff0c;通过光合作用吸收二氧化碳&#xff0c;制造氧气&#xff0c;同时为其他生物提供食物和栖息地&#xff0c;支持它们的生存。植物在生态系统中还起到防止水土流失、缓解温室效应等作用。…

新手小白编程利器!Debug 断点调试工具IDEA

前言 很多新手小白在学习的时候总会遇到一个问题&#xff1a; 我们一运行程序&#xff0c;只能看到程序最后的结果&#xff0c;但是这个程序究竟是怎么一步步运行出这样的结果呢&#xff1f;如果有一个工具能够让我们看到我们程序的执行流程该有多好~ 这就需要用到新手小白编程…

modbus转MQTT网关支持自定义JSON格式

在工业自动化系统中&#xff0c;Modbus是一种非常常见的通信协议&#xff0c;而OPC UA则是近年来兴起的一种新型通信协议。由于各种设备之间使用的通信协议不尽相同&#xff0c;因此需要一种能够实现多种协议转换的网关产品。BL110网关就是一款能够实现Modbus到OPC UA转换的产品…

硬件设计-PLL篇(下)

目录 概要 整体架构流程 技术名词解释 技术细节 1.环路滤波器采用有源滤波器还是无源滤波器&#xff1f;、 2.如何设计 VCO 输出功率分配器&#xff1f;、 3.如何设置电荷泵的极性&#xff1f; 4.锁定指示电路如何设计&#xff1f; 小结 概要 提示&#xff1a;这里可以添加技术…

学习Spring的补充

《JavaEE 框架整合开发入门到实践 》这本书还是太浅了 &#xff0c;需要补充一些基本的知识。 首先修改一下快捷键&#xff0c;使得可以像eclipse一样使用alt/ 快速补全代码。 参考博客&#xff1a; (33条消息) IDEA 设置代码提示或自动补全的快捷键_idea补全提示_thirty.st…

【Java核心技术】面向对象编程

面向对象编程 1、面向过程与面向对象面向过程(POP)与面向对象(OOP)面向对象的三大特征面向对象的思想概述 2、Java语言基本元素&#xff1a;类和对象类和对象类和对象的使用&#xff08;面向对象思想落地的实现&#xff09;对象的创建和使用&#xff1a;内存解析 3、类的成员之…

【瑞萨RA6系列】CoreMark移植完全指南——UART输出和SysTick计时

一、CoreMark简介 什么是CoreMark&#xff1f; 来自CoreMark首页的解释是&#xff1a; CoreMark is a simple, yet sophisticated benchmark that is designed specifically to test the functionality of a processor core. Running CoreMark produces a single-number scor…