聊聊单元测试

我是一个着迷于产品和运营的技术人,乐于跨界的终身学习者。欢迎关注我的个人公众号「跨界架构师」

每周五11:45 按时送达~

我的第「167」篇原创敬上


大家好,我是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)之类的开发方式恰恰是敏捷开发实践的重要组成部分,但是我们却嫌弃它拖慢迭代速度。

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


推荐阅读:

    ■ 心想技术驱动业务,却在背道而驰

    ■ 软件如何优雅地向前兼容?


如果你喜欢这篇文章,可以点一下右下角的「爱心」,支持我的创作~

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些深度思考。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 作为一名质量管理人员,从刚入行时就接触到单元测试:需求提测时要保证一定的单元测试覆盖率作为提测准入;进行线上问题c...
    Rechel_uniq阅读 683评论 0 1
  • 遇到问题多思考、多查阅、多验证,方能有所得,再勤快点乐于分享,才能写出好文章。 一、单元测试 1. 定义与特点 单...
    程序熊大阅读 8,202评论 7 62
  • 本篇主要是聊一聊以下几个方面的内容: 为什么要单元测试 单元测试框架 单元测试的好处 单元测试与重构 1. 为什么...
    塞外的风阅读 237评论 0 3
  • 正式工作之后,公司对于单元测试要求比较严格。(笔者之前比较懒,一般很少写完整的单测~~)。作为一个合格的开发工程师...
    代码人生ll阅读 8,400评论 0 2
  • 正式工作之后,公司对于单元测试要求比较严格。(笔者之前比较懒,一般很少写完整的单测~~)。作为一个合格的开发工程师...
    LeeHappen阅读 2,084评论 1 10