什么是UT?

UT(Unit Test)即单元测试

UT有什么价值?

大部分的开发都不喜欢写UT,原因无非以下几点:

  1. 产品经理天天催进度,哪有时间写UT

  2. UT是测试自己的代码,自测?那要QA何用?

  3. 自测能测出bug?都是基于自身思维,就像考试做完第一遍,第二遍检查一样,基本检查不出什么东西

  4. UT维护成本太高,投入产出比太低

  5. 不会写UT

总之有无数种理由不想写UT,作为工作不到三年的菜鸟深有体会。之前在点评工作的时候,团队的“UT”都集中于RPC的服务端。为啥带双引号? 因为RPC的服务端没有页面可以功能测试,部署到测试环境测试太麻烦,只能写UT了。在这个场景下我认为叫“验证”更合适,验证不等于测试。 验证往往只写主逻辑是否通过,且就一个Case,且没有Assert,有的是System.out。

本人实习的时候做测试的,那时候知道一个测试模型。如下图:

(图一)

图的意思就是越底层做的测试效果越好,越往上则越差。也就是说大部分公司现在做的功能测试其实是效果最差的一种测试方式。 另外,QA界有个现象:大家都知道功能测试没技术含量,那如何使自己突出呢?答案就是:自动化测试。现实是没几个公司能做好自动化测试, 业界做的比较好的百度算一个。那么为啥自动化测试这么难做的?在这个模型当中,越往上黑盒越大,自动化测试难度就越大。 这句话反过来就是越往下自动化测试就越好做?没错,UT其实是最容易实现且效果最好的自动化测试。 所以在很多公司出现一种现象:QA写UT。
原因总结一下就两点:开发不愿意写UT,QA想自动化测试解放自己。 以上的模型只是理论上说明UT具有巨大的价值,但是真的如此么?我只想说,只有真正尝到UT的好处的甜头才会意识到UT的价值。

Unit Test & Integration Test

单元测试和集成测试的界线我相信大部分开发也是不清晰的。个人理解单元测试针对于一块业务逻辑最小的单元,太抽象。物理上可以简单理解为一个类的方法, 可以是public方法也可以是private方法。一个单元测试不应该包含外部依赖的逻辑,反之就是集成测试了。 问题的核心就在于此。一个service的一个接口实现可能依赖很多第三方:1.本地其它的service 2.dao调用 3.rpc调用 4.微服务调用。如下图:

图二

也就是说你的单元测试,真正调用了外部依赖那就是集成测试。这其实很常见对不?我们先说这种情况下如何集成测试。

Local Integration Test

本地集成测试也就是说不依赖与其他进程。包括:service依赖其他本地service或者dao的情况。在讲述如何集成测试之前,我们先理一下测试模型,测试主要包含三块内容:1.数据准备 2.执行逻辑 3.输出验证。

第一步:数据准备

在本地集成测试里,数据来源基本上来自于dao,dao来自于sql。也就是在执行一个case之前,执行一些sql脚本,数据库则使用h2这类memory database, 切记不要依赖公司测试环境的db。下图是使用spring-test框架的一个case,可以在case执行之前准备我们所需要的各种数据, 另外在执行完case之后,执行clean.sql脚本来清理脏数据。这里也说明一个case的执行环境是完全独立的,case之间互不干扰,这很重要。

第二步:执行逻辑最简单,就是调用一下我们测试的方法即可

第三步:验证

集成测试一般是调用service,或者dao的接口验证。

举个例子:CRUD操作的集成测试

  1. 调用C接口

  2. 调用R接口,验证C成功

  3. 调用U接口

  4. 调用R接口,验证U成功

  5. 调用D接口

  6. 调用R接口,验证D成功

Remote Integration Test

假设我们一个service实现依赖某个RPC Service

第一步:数据准备

跑到别人家的数据库插几条数据?或者跟PRC Service的Owner商量好,搭一个测试环境供我们测试?有些公司还真有专门的自动化测试环境,那么即使有测试环境,那如何实现各种case场景下,第三方Service很配合的返回数据给我们?想想都蛋疼。

第二步:执行方法

假设我们成功的解决了第一步中的问题,皆大欢喜。现在来看第二步,假设我们的service里面调用了另一个RPC Service创建了很多数据,跑了无数次case,结果….RPC Service对应的数据库都是我们的脏数据,如何清理?而且他们敢随便删数据吗?想想也蛋疼。

第三步:输出验证

假设我们又愉快的解决了第二步中的问题。现在来看第三步,假设我们的方法执行最终输出是创建了一个订单,订单当然是调用订单Service接口了,那么我们如何验证订单是否成功创建了呢?或许可以调用订单Service查询订单的接口来验证。很明显大多数情况下并没有这么完美。想想也蛋疼呀。

通过以上分析,Local Integration Test是可行的,Remote Integration Test基本不可行。

那么有没有什么办法解决呢?答案就是Mock

  • 第一步:Mock RPC Service 想返回什么数据就返回什么数据

  • 第二步:还是Mock接口,想调用几次就调用几次

  • 第三步:这一步等到下面讲完单元测试就明白了

Unit Test

上面我们谈到Mock可以解决外部依赖的问题,现在有很多Mock的开源框架比如:mockito。那么问题来了,既然我们可以mock第三方远程依赖,为何不mock dao、local service呢?没错外部依赖全部mock掉,就是单元测试了。因为我们只关心所测试的方法的业务逻辑,也就是真正高内聚的逻辑单元了。如下图:

(图四)

好处如下:

  1. 没有什么数据是造不出来的,通通返回Mock的对象

  2. 代码中的异常处理代码,也可以通过mock接口,使之抛出异常

  3. 不产生任何脏数据

  4. 跑case更快了,因为不用启动整个项目,相当于Main方法

有人会说,都mock了还测试个蛋蛋。

这就是对于单元测试的理解了,单元测试应该只针对于目标方法的业务逻辑测试,dao、其它service应该在它们自身的单元测试去测试。对于依赖的第三方,我们应该信任它们能正确的完成我们所预期的。这句话很难理解对不对?

举几个例子

例子一:方法的最后是执行dao的create操作,那么该如何验证?

我们应该验证的内容是:

  1. dao的create方法被调用了

  2. 调用次数是对的

  3. 调用参数也是对的

没错,只要这三个验证通过,那么这个case执行就是通过的。因为我们相信dao的create操作能正确的完成我们所预期的,只要我们调用了正确的次数并且参数都是对的。dao的执行的正确性保证是在该dao的单元测试做的。 在Remote Integration Test里面第三步验证道理是一样的,我们应该验证RPC接口被调用了且次数和参数都是对的,那么我们的case就算通过了,至于,RPC服务端是否正确执行是它们的事情不是我们所关心的。 Mockito框架的verify接口就是做这件事情的。如果你理解了上述内容,那么你就开窍了,UT不在变得这么难写。

什么时候用单元测试,什么时候用集成测试?

在本人的实践中摸索发现,对于简单的业务,比如crud型的瘦service,比较适合于集成测试。

以下情况适合于单元测试:

  1. Util类

  2. 含有远程调用的方法

  3. 输入少,业务逻辑复杂的方法

  4. 需要异常处理的方法

case细到什么程度为好?

这个问题也是比较经典的,一个方法要是所有的路径都覆盖到,那么要写很多的case,说真的累死人。我的建议是两个原则:

  1. 核心逻辑,容易出错的逻辑一定要覆盖到

  2. 根据自己的时间。 没必要写的非常多,毕竟case维护成本很高,业务逻辑一改,case得跟着改。

总结

本人目前在从事于开源项目(Apollo(配置中心))研发,开源项目对代码质量要求相对来说高一些,UT当然是很重要的一环。刚开始也不会写UT,当然态度上也不重视UT。老大的代码UT覆盖率很高,抱着对开源负责的态度慢慢接受学习UT,到后来尝了几次甜头后,发现UT真的很实用,价值也很高,但是很遗憾UT被大部分开发所忽略。当然本人对UT的理解、实践还不够,仍需继续实践模式。

最后说一句:当开发完功能,跑完UT,你可以放心的上线了的时候,你的UT就成功了。

小谈 Java 单元测试相关推荐

  1. 浅谈java单元测试框架junit4/5

    0 前言 junit是一个开源的Java语言的单元测试框架.目前junit主要有版本junit3,junit4和junit5.因在junit3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有 ...

  2. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  3. java编程double相乘_浅谈Java double 相乘的结果偏差小问题

    看下面的一段代码的运行结果: public class TestDouble { public static void main(String[] args) { double d =538.8; S ...

  4. 猿来小课Java视频教程讲师浅谈JAVA体系结构

    猿来小课Java视频教程讲师:Java体系结构中不仅定义了Java的开发编译环境,也定义了Java的运行环境.为运行Java应用程序和applet,计算机上应安装JVM和Java运行时解释器,这两个部 ...

  5. java jquery 框架_[Java教程]小谈Jquery框架

    [Java教程]小谈Jquery框架 0 2013-12-23 18:01:16 现在Jquery框架对于开发人员基本上是无人不知,无人不晓了,用起来十分的方便,特别是选择器十分强大,提高了我们的开发 ...

  6. java单元测试如何全覆盖_单元测试代码覆盖率的浅谈

    在做单元测试时,代码覆盖率通常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况.比如,代码覆盖率必须达到80%或90%.于是乎,测试人员费尽心思设计案例覆盖代码,用代码覆盖率来衡 ...

  7. Google首席架构师谈Java的命运

    Google首席架构师谈Java的命运 文 / Peter Seibel 译 / 郝培强 本文是Common Lisp专家Peter Seibel对Google公司首席Java架构师Joshua Bl ...

  8. java的byte php_java_浅谈java的byte数组的不同写法,(由于篇幅原因阐述的不够详 - phpStudy...

    浅谈java的byte数组的不同写法 (由于篇幅原因阐述的不够详细科学,不喜勿喷). 经常看到java中对byte数组的不同定义,粗略整理的一下: 一个字节(byte)=8位(bit),"b ...

  9. java接口与类相同不同_浅谈java的接口和C++虚类的相同和不同之处

    C++虚类相当于java中的抽象类,与接口的不同之处是: 1.一个子类只能继承一个抽象类(虚类),但能实现多个接口 2.一个抽象类可以有构造方法,接口没有构造方法 3.一个抽象类中的方法不一定是抽象方 ...

最新文章

  1. 骂战之后,天才王垠或将加入华为,赵海平怒离职,加入字节跳动!
  2. think in java interview-高级开发人员面试宝典(三)
  3. 安卓Android科大讯飞语音识别代码使用详解
  4. 【LDA学习系列】Dirichlet分布python代码
  5. 云原生时代 RocketMQ 运维管控的利器 - RocketMQ Operator
  6. python 抓取目录树_python 获取文件下所有文件或目录os.walk()的实例
  7. 程序员修神之路--用NOSql给高并发系统加速
  8. python watchdog休眠_python watchdog
  9. 蔚来Q3营收近百亿、毛利率20.3%,预计明年下半年推出2款新车
  10. 能自定义桌面后,iOS用户玩疯了
  11. 想入职阿里的Java开发者必看,阿里巴巴面试官实战经验分享!
  12. python 菜鸟-Python3 列表
  13. log4j MDC用户操作日志追踪配置
  14. 衡量高分辨率图片生成效果的指标PSNR/SSIM
  15. 肝了这篇文章,我对服务器硬件有了深刻的认识!
  16. 华为云教程(弹性云服务器ECS)
  17. 解决 Git 报错:GitHub - failed to connect to github 443
  18. Typora搭建图床解决上传md文档图片无法加载的问题
  19. 时间复杂度分析-大O表示法
  20. POE原理,千兆POE供电分离方案

热门文章

  1. 来自一名程序员的表白
  2. Developer Tools
  3. ync 小技巧-14-为用户启用统一的联系人存储库-Lync-无联系人
  4. Quartz教程二:API,Job和Trigger
  5. java中的sleep()和wait()的区别
  6. 用户目录更改后显示为-bash-3.1#
  7. ccna学习指南笔记9
  8. 浅读:ITSM信息技术服务管理
  9. 【深度学习】2个经典的练手CNN源码与MNIST数据集测试结果
  10. Python-简单优先队列