一个初级码农的测试之旅(一)——初识单元测试

前言

首先说一下我自己——一个码农,准确的讲我是一名在中国最大互联网公司搬砖的初级码农。我不是计算机科班出身,一年前进入公司的时候,从未接触过web开发,没有完整的学习过数据库知识,写不出一条完整的sql语句,甚至不知道js和css到底是怎么控制页面行为和样式的——这样的人为什么可以通过面试?反正不是因为我长得帅。

背景知识

文章最初,先介绍一下我们团队的产品——阿里云持续交付平台(crp.aliyun.com),是一个旨在服务阿里云上众多开发者的持续交付平台(你可能还没听说过,但不妨一试哦),产品以nodejs实现,采用express框架。

接触单元测试

经历了入职最初的摸索之后,我逐渐掌握了上面提到的各种不会,开始可以用相对“丑陋”的代码来实现产品的需求,表面上看起来实现得也还不错。
为什么是表面上?因为在两次较为重要的发布之前,我们总是修复bug到凌晨之后,调试的办法当然是大家最熟悉的console.log…用过nodejs的人都知道,天生的异步会让console.log变得异常艰辛。
为什么会在上线前的人肉测试时才出bug?因为我们当时的代码是裸奔的,所谓的“赶进度”让我们时常提起单元测试的重要性却每每都在完成需求之后就去领取新的story了。
让我这个初级码农最终走上单元测试这条不归路的原因,除了上述的痛苦经历,还有一个更直接的原因——团队来了一位真正懂并且会写单元测试的哥。可以说,本文就是在他逼迫之下完成了一些个单测之后又在他“逼迫”下最终成文的。

什么是单元测试

几乎所有程序员都会说出单测的重要,但单测到底是什么,为什么重要,又应该怎么去写呢?
对于单元测试的定义,我们可以参考相对严谨的维基百科中的词条(https://en.wikipedia.org/wiki/Unit_testing) ,简单翻译就是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作,而一般的分歧则在于对最小单位划分的定义,对于这个分歧我无意深究,因为根据项目实际情况来划分会显得更加合理。

为什么选择单元测试

按照上述的定义,单元测试只能涵盖最小单位本身,并不能确保系统整体的正确性,既然如此,为什么不直接采用更全面更真实的集成测试(https://en.wikipedia.org/wiki/Integration_testing) 或者功能测试(https://en.wikipedia.org/wiki/Functional_testing) 呢?原因相信很多人都知道,这是在成本与收益之间选择了折中。成本一方面体现在复杂度,对3个模块(假设每个模块有n条路径)的单元测试,我们需要考虑n+n+n种可能,但如果是对三个模块做功能测试,那么我们则需要考虑n*n*n种可能,难易程度一目了然;成本的另一方面是时间,我们期望用5分钟甚至更少的时间去发现80%甚至更多的错误,而剩下20%左右的错误用集中的更长的时间去处理,毕竟一次代码提交之后,程序员希望尽快知道结果,以决定是修复bug还是开始新功能的开发。

两个简单的例子

毫无疑问,写单测会增加“额外”的工作量,特别是当你还不懂得如何灵活使用并驾驭测试框架的时候。但当一切都步入正轨的时候,事情就会变得美妙起来。下面我将简单的举几个nodejs的单测例子来详细说明:

示例一

某天我接了一个计算pipeline触发时间的需求,基本要求是根据触发的具体时间,换算成n秒前、n分钟前、n天前这样的显示格式(超过一年的则直接显示具体日期)。开发难度并不高(友情提示:实在不会,百度或者Google一下都有类似你想要的答案哦)。但是,我应该怎么向产品经理证明我的程序是没问题的呢?“一分钟前”、“一小时前”哪怕“一天前”都还好,但“五个月前”甚至于一年前的日期要显示具体日期,我总不能等一年才通过验收吧!这种时候,单元测试可以替我说话:
首先,我们需要准备数据:

before(function() {
    instanceTriggerInfos = [{
       instanceId = 1,
       operationTime: new Date((new Date()).getTime() - 2  60  1000)  //两分钟前
    },
    {
       instanceId = 2,
       operationTime: new Date((new Date()).getTime() - (3  24 + 4)  60  60  1000)  //三天前
    },
    {
       instanceId = 3,
       operationTime: new Date('2015-1-1')  //2015年1月1日,超过一年了
    }];
});

我写了一个方法叫做formatDate,对计算结果进行判断,于是我可以写这样一个测试。

describe('function #present', function() {
    it('should only return the maximum unit', function() {
    var presentation= formatDate (instanceTriggerInfos);
        expect(presentation[instance1Id].time).to.equals(2);
        expect(presentation[instance1Id].unit).to.equals('分钟前');
    expect(presentation[instance2Id].time).to.equals(3);
       expect(presentation[instance2Id].unit).to.equals("天前");
       expect(presentation[instance3Id].time) .to.equals('2015-1-1');
   });
});

As you wish, all test pass!于是乎,我可以开开心心地交差并迎接新的挑战去了!
等等,如果需求变成了超过一年显示“1年前呢”?那我在修改代码之后,只要把最后一个断言改成如下,并通过测试是不是就可以了呢?

expect(presentation[instance3Id].time).to.equals(1);
expect(presentation[instance3Id].unit).to.equals("年前");

其实,在面对需求变更地时候,我们甚至可以改变顺序,即先修改断言,然后去修改代码,同样以代码通过测试为验收标准——有没有觉得一丝丝地耳熟,没错,这就是传说中的测试驱动开发(TDD)。

示例二

我们的产品(crp.aliyun.com,广告Again)具有定时执行任务的能力,具体实现是将定时配置丢到redis里面,然后定时去redis里面提取可以在当前周期执行的任务,并执行之。所以当用户重新配置过之后,我们需要将旧的配置删除,并创建新的配置。有一天发现在修改过之后,旧的配置并没有被删除,于是乎去追代码、找bug(没错,这段功能没有单测)——不久便找到了根源,这段代码大致如下:

var deleteSchedule = function(scheduleId) {
    var jobs = getJobs(); //获取所有job
    var targetJob = _.find(jobs, function(job) {
        return job.id === scheduleId;
    });//找到与scheduleId匹配的job
    deletetJob(targetJob);//删除targetJob
};

问题出在

job.id === scheduleId

这一判断条件,通过console.log得知传入的scheduleId是一个object:

{
    id: scheduleId
}

“万恶”的动态语言,程序不运行至此,根本不知道入参int还是object。所以我直接将判断条件改为

job.id === scheduleId.Id

并git push。此时,发生了一件忧伤的事情:
由于我们在本地给git配置了一个 pre-push的hook来自动运行单测,如果单测不通过,则拒绝push。而此次push就恰恰被拒绝了。转念一想,也是一件好事,说明我们的单测发挥作用了呀。
看了测试代码后才发现,原来deleteSchedule这个函数在另一个地方也被调用了,而那里传入的scheduleId是int,所以更加合理的修改应该是在是上述的调用处,将入参从object改为int(更符合入参名称)。这次改完之后,我没有直接push,而是老老实实的给此处调用该接口的函数加上了单测…
所以在这个例子中,单元测试不仅仅帮我发现了bug,而且还督促我把代码写的更好。

结语

作为一名立志于不断打怪升级的程序员,上面提到的测试也仅仅是测试之旅的开始,后面我会继续分享如何选择和使用单元测试框架,如何准备数据来写集成测试等等。期待与大家一起交流,让测试不再是一句口号。

时间: 2024-08-03 10:01:46

一个初级码农的测试之旅(一)——初识单元测试的相关文章

一个老码农的技术理想

小时候,老师问我,你的理想是什么?我不假思索说是工程师,于是长大之后果然成了工程师. 工作这么多年,一直在思考工程师这三个字的意义,终于有一天恍然大悟,原来就是:用技术手段改进世界. 那么,在软件方面,目前的世界有哪些问题需要解决呢?有这么一些问题可以思考: 现在整个世界的信息化程度是偏高还是偏低? 程序员的人数够用吗? 软件行业的生产力是偏高还是偏低? 大部分软件系统都可靠吗? 我想说说自己对这几个问题的理解. 虽然现在我们的生活与十年前相比,已经发生了巨大变化,比如智能手持设备已经非常普及,

看看 “悲催” 的码农得学多少东西?

首先你得会一门编程语言,比如 C, Java, Python, PHP 等,但是光语言本身还差得远,还得学习这门语言的类库,学完类库还得学框架,Spring, Struts, Hibernate, MyBatis 每一个都不是善茬. 做前端的同学也不用说了,HTML/JavaScript/CSS,再加上一大堆前端框架 Angular, React, Vue,总得学会一样吧. 学完了框架才能做项目,找工作,才有可能开始光荣的增删改查之旅. 但是这还远远不够,前面说的这些知识只能算是入门. 数据结构

阿里巴巴六年码农自白

各位亲爱的网友朋友大家好,又到了回顾上周新闻的时候了.上一周热闹的事情比较多,我们把他们拆开来看一下. 首先看一下开发者感兴趣的话题.一提到开发,大家可能都会想到一个形容词,那就是"辛苦".的确,开发人员的工作量非常的大,所以很多开发人员被冠以了"码农"的称号.上周点击量第一的文章,标题就叫"一个阿里巴巴码农的六年回眸"这篇文章讲述了一个码农的六年心路历程,中心思想总结起来就是:"技术耐得住寂寞,低谷积累高峰冲刺,主动改变一切.&quo

码农故事:一个辞职创业卖凉皮的程序员

我出生在陕西西安农村家庭,从小学习也一般,记得我们上幼儿园的时候,学生们都背着家里给自己用布缝制的书包,教室的窗口是用农村的白色蛇皮袋子,到了冬天北风呼啸,教室里因为没有桌子板凳而风从窗口呼啸. 从小学开始我就特别羡慕那些学习特好的同学,羡慕他们快速的解题速度,每次有很多和我一样的人都拼命非常努力,但是总是成绩平平,所以我们每天的生 活都是做题,解题,高分数,你每天都得跟别人比,渐渐让我感觉从此你的人生就只能平庸,学习好的学生上好的学习,找好的工作,挣高的工资,尤其在我们印象 最深的是高考,三天

为什么你说“就差一个码农了”,我们是拒绝的

根本原因是 90% 这么说的人不懂技术,不了解行业,把技术实现想太简单,以为编程就是打字.分开来说包括以下 4 个方面:不尊重程序员.不只缺一个.大大低估投入.还没到需要程序员的时候,下面一一说明. 不尊重程序员 要一起共事的基础是相互尊重,古语讲"礼贤下士"是有道理的,人才需要得到相应的尊重,但是很多人在发"就差一个码农"招人帖时是不注意这一点的. 如果你需要一个程序员,而你招人的标题真的是"我们就差一个码农了",那多半大家看了标题就会来喷的,

年薪三十万的码农不如一个省委办公厅公务员吗?

简评:程序员也好省委办公厅的公务员也好,其实每个人都有自己的选择,不必却质疑别人的路,不用艳羡别人的生活,活出自己就好了.对于我而言,曾经对未来也有很多憧憬或者是不切实际的幻想,但是现在我选择了我的路,我认为程序也能让这个世界变得更美好,没有什么值得和别人攀比的,与君共勉. 这是春节期间水木上一个非常火的帖子:水木社区-源于清华的高知社群     水木原贴:     都是我们村的后辈,三个都研究生毕业,都25岁左右,你们觉得这两个人谁厉害些?     1.男A,北邮本硕计算机,毕业进了网易游戏,

一个经验尚浅的码农五年软件开发的一点自我总结,对工作五年的反思~

class="post_content" itemprop="articleBody"> 一位就要换城市(离开北京)+ 换职业(不做开发),去新公司报道的程序猿,最近反思了自己毕业后的这五年工作,记录下五年以来软件开发的一点自我总结,也算是给过来人提点建议,少走点弯路吧-- 08年顶着名校硕士的光环加入了一家非常有名的非软件公司做软件开发,刚开始一切其实都很美好.大外企的各种好在头一年给自己带来了很多光环,当然自己也学到了很多(主要是非技术的东西).可是从第二

一个年薪50万的IT码农哥辞职卖咖喱凉皮的故事

前一个月码农哥从互联网行业直接跳入苦脏累的餐饮行业, 在开业之前码农哥曾写了一篇文章叫<十年码农自述:我为什么辞职卖凉皮>, 其实码农哥想表达的主题思想是:想做出好的产品需要傻子思维, 聪明人容易自认为聪明,所以他会把一个问题复杂化,认为需要思考的东西才有价值,因为他们最喜欢有智商挑战的东西,但是往往会聪明反被聪明误.传统工业化时代的标准思维模式是:大规模生产.大规模销售和大规模传播,这三个大可以称为工业化时代企业经营的"圣三位一体", 但是互联网时代,这三个基础被解构了.

如何伪装成一个年薪 20 万刀以上的码农?

初级版 1.有原则的在电脑上贴Sticker 你的电脑上如果只贴了一个"苹果",那一起来把基础的逼格提升一下吧! HTML.Dropbox.Flickr-甚至NSA,要想成为一个牛逼的码农,先在电脑上贴满象征性的Sticker! 2.烧毁你的西装和皮鞋 怎么样可以用最高调的形式告诉大家,你不是来自湾区的野生程序猿嘛?就是穿上从东岸带来的西装皮鞋,头发混入半瓶发胶,笔直又忐忑地坐在星巴克等你的朋友.(神秘的微笑) 哈哈哈,大家猜猜这是谁的衣橱??? 野生程序猿Outfit:一件T配上牛仔