0%

呼,紧张的一周复习+赶due,终于在今天结束了NEU的第一个学期。我认为这里的教学质量是扎扎实实的,我选的每一门课都很有挑战。我自己也是比较投入到学习里,还是非常希望巩固本科学得稀烂的基础学科:算法和网络。考试分数的话我觉得挺悬,因为自己现在考试会紧张,有时候简单的题居然都会卡一会儿反复验证。而且在这种紧张下,一些看似掌握了的题目一到考场变化一下,限时的情况下就做不出来了,比如动态规划。所以说题目还是得多去做些,天赋没那么高就多努力做题呗,也趁着期末复习的劲儿开启leetcode刷题。

一个阶段结束,就要开始思考下一阶段的规划了。目前自己的状况比较尴尬,首先手头现金已经不多了,下学期应该要做一些兼职工作来缓解压力。其次是找实习,大厂们23夏季的实习窗口已经过去了,而我刷题的量还没起来,更尴尬的是我的前端履历并不能给我后端实习添加优势。所以下一阶段的重点肯定是围绕如果找到满意的实习进行。

Read more »

终于重新拾起笔记录生活了,先给自己点个赞2333🤪

这是来美国之后第一次记录生活,从八月到现在过的很充实精彩,简单总结下吧:

Read more »

哟伙计,被我标题党骗进来了吧👻👻

哎哎别走,虽然标题取得很有误导嫌疑,但是我可没撒谎,我真的在听MIT的课哦——就是那所QS全球大学排名连续11年霸榜No.1的麻省理工大学 (Massachusetts Institute of Technology),单项的话如计算机科学的CS Ranking也高居全球第三!

Massachusetts Institute of Technology

看了上期周刊的朋友肯定会说你可拉倒吧,你个东北大学生搁这装💩 好吧我招我招,我听的是由MIT发起的OpenCourseWare平台的在线课程,这是一个拥有超过2,500门课程的免费公开课平台,致力于免费分享最优秀的知识给全世界的学习和教育者!

Read more »

Hi 朋友们!下了一周雨的广东终于迎来一个晴朗的周末,舒服了😌

本期周刊打算分享我本人在2021年10月初开始起意,边工作边着手美国研究生的申请,12月中旬递交所有申请,最终在4月初收到梦校Offer的经历📚 目的是整理这段经历,跟大家坐下来分享一份属于打工人成功学术上岸的案例✅ 我的背景和经历也许并不普适,但如果其中有环节的某些信息能给到你帮助,那我会非常开心👾

那么,正题开始!🛫

Read more »

Hi,朋友们!我是布鲁斯基👾

距离上次发文章已经过去快两个月,真就大懒鸽了属于是🐦 在家里的这两个月,对我来说时间过得不算很快,因为这期间我感受、思考、经历了许多,数次有话想讲,又因为各种原因提笔放下(我承认懒惰占九成🤪

而今天,我终于又燃起动力写文章了!这次想聊下“个人周刊”,一种很适合个人”短平快“输出的内容形式

Read more »

在提交完最后一次整理项目REAMDE的merge request之后,我合上了Macbook——我的老战友,跟我的公仔们还有工卡放在一起,咔嚓——来了张合影。

没错,我的鹅厂生涯暂时要告一段落啦~


说起离职,我其实有过好几次冲动,每次的理由都不一样哈哈,然后又会听取朋友或前辈们的意见,冷静下来。但这一次,我做出了决定,如果要总结原因,我觉得是到“时候”了。

这里的“时候”,可以理解为三层意思:

  1. 是圆我的留学梦的好时候了
  2. 是时候重视生活与工作的平衡了
  3. 是时候想清楚我想成为什么样的人了

我想“行万里路,读万卷书”

去年9月的时候,朋友微信上突然跟我说:“美国Master今年申请不要求GRE哎!”

我查了下几所知名的大学,GRE确实是Optional了??这一消息把原本打算22年(工作满3年嘛)才开始准备留学的我,提前赶上了跑道。因为早就耳闻GRE挺麻烦的,免去了GRE相当于抄底申请呀哈哈。

我以为我英语还不错啊,结果第一次托福摸底考了个70分…于是在10月份的时候,我报了下线1对1托福班,开始了早六晚十二的边工作边考语言的生活(早上六点半爬起来背单词,中午饭后刷口语,午休刷阅读,晚上7点下班刷写作TUT)。还在墙上列了排期表,每过一天就叉掉,不断给自己正反馈。

经过了2个月的超自律的突击,我赶在11月底考出了99分(就差1分到100,真是气死人)…

嘛,看了下官网好像不是强制100分,那就这样吧,赶紧搞文书去,最终在名校DDL12月中旬前,申请了4所美国(CMU@MSCS, UCLA@MSCS, UCSD@MSCS, NEU@MSCS)、2所香港(HKU@MSC, CUHK@MSC)、1所新加坡(NTU@MSAI)。

出国留学这事儿,对我来说学历镀金可能只是个结果,我更在意的是出去看看、静心学习的过程。 我想趁还没有到被年龄、婚姻等束缚的年纪,满足我探索这个世界的好奇心。同时我相信大部分学生出来工作后,都会想念大学时大片的可支配时间吧!只可惜本科时玩心太重,没有把时间用好,现在带着目的重回校园,希望能不负韶华,学到真材实料。

至于为什么是美国,当然也是因为那边能看到、体验“全球化”的缩影,开阔视野,求同存异,广交人脉;也因为美国是计算机的发源地,去源头学习CS与工作,肯定会有不一样的收获吧。疫情和安全?在跟几个已经在美国的朋友聊过之后,其实做好个人防护和选相对安全的城市生活,还是有应对和避免的策略的。

所以现在有“行万里路,读万卷书”的机会,我当然愿意去争取!(保佑我三四月份能收到一份offer吧

我想要健康、能陪伴家人、完整的日常

2019年毕业体检,我就检查出有胆囊息肉(6x4mm),到了2021年7~12月分别测了两次,胆囊息肉从8mm长到10mm,长得太快了!医生说超过10mm就要切除胆囊了,对,是切除胆囊不是息肉,主要是怕胆囊病变。

胆囊息肉的成因就是油腻、饮酒、熬夜与压力。大学时比较浪,喝酒烧烤熬夜啥的;工作之后,我在熬夜和饮酒都有所克制,剩下的就是压力了。虽说我在的部门有双休(大约985的工作节奏),工作日能够按时完成业务就不错了,然而还有绩效和个人成长会一直萦绕心头,让我在休息时还会想要抓紧学技术。离谱的时候周末都会掐着表规划和学习,很多时候甚至会忽略家人和女朋友的感受,自己给自己压力太大,把自己整得焦虑不堪。

还有一件事让我压力倍增。去年8月,前leader拉我垫背二星。我上半年接了他甩来的联合项目锅,摊子蛮烂的说实话,人力少又服务多个业务部门,已经认真尽心尽力地在做,时间都耗上面了。结果因为换组该项目不是组里的目标了,他拿我没有技术输出来说事儿,硬给二星。一那个项目就是技术中台,提升了业务运营效率,到他那就不算技术输出;二是组内的技术项目我也有在参与和设计,他老早不关心代码了就选择忽视说没看到呗。真是受不了这侮辱,直接开怼+要走。虽然我后面还是冷静下来换组了,但对我后续的工作无形增加了很大的压力,毕竟拿个二星,年终少了不说,晋级要比正常人晚2个周期,哎。

槽吐完了,回到身体健康的话题来。我妈心软,看我压力大,常常劝我说“人生很长,做任何事都不用急这一会儿,但身体健康是关乎一辈子的”。大厂本来就注定是紧张快节奏的,加上自己的精神和身体状态都不太好,所以我决定Slow it down,让自己喘口气,调整身体和心态,再出发。

BTW,去年10月份突如其来的车祸夺走了我的外婆,也打碎了我美满的家庭。外婆勤快,烹饪家务样样拿手,有外婆在的家,是温馨而井井有条的,每次回家都能看见她慈祥开心的笑容,吃到她特意准备的饭菜。她的离去真的给我们打击很大,尤其是我的母亲。我想大学之后就没好好在家待过了,如果我22年拿到offer,8月就会前往异国,又要离家很久。我想多陪陪家人,外婆不在了,家里就由我来打理吧。

暂时告别看不见日落的互联网打工生涯,呼吸、生活才是人生的常态啊。

我想成为Web3.0的独立工作者、创业者

想起21年底游戏大神轻生的事件,有条知乎高赞回答用故事的方式尝试表现互联网人的承压工作现状:远大的理想与骨感的现实,超强的能力与过载的期望,百万年薪与高强度的工作,成功的事业与垮掉的身体,这些对立的矛盾真的刺痛到我的神经。

我到底想要什么,想成为怎样的人呢?在我已经过去2轮本命年的时候,我想我应该有一个大致的答案了。花了几个晚上,在画布上用脑图列出所有我能想到的方向,自问自答,最终没有得出具体的结论,但是承认了一个事实:“我只是一个普通人,我能选择从事感兴趣、有价值的领域就够了”。

Web3.0,这一2021年兴起的区块链概念,阐述了信息即资产的新一代互联网设想。依托于区块链与密码学技术,每个网络公民将对个人信息或生产的信息有着更强的自主权,同时享有收益的权利,这也就是信息即资产的意思。同时加密货币、NFT、DAO、DeFi、GameFi等生态正在吸引全世界的弄潮儿们加入,探索巨头垄断、红利逐渐枯竭的Web2.0时代的新解决方案。将制度交给数学与程序来实现,探索无人区,这对于技术人员来说,是最好的时代;对于非富二代官二代的年轻人来说,也是世界洗牌、阶级跃迁的好机会。

Web3.0的建设者来自全世界,自然不可能都在一处办公,所以成为独立工作者的想法就冒出来了。成为独立开发,意味着我可以选择方向、项目,意味着我可以把控work life balance;当然也意味着收入不稳定、需要超强自制力等等要求。但总体来说,它很吸引我,也天然适合程序员这个职业。

再仔细想想,其实独立工作不意味着单打独斗呀,我还可以找到志同道合的伙伴,一起合作作战,甚至日后创立属于我们的项目和事业。

另外对于加密货币的世界,无疑也是黑客们的黄金时代,网络安全和区块链安全将是hacker们激战的领域,这也是有意思的领域~

其实到了这里,我的视野和能力已经限制了我的想象,产生了很多不确定的不安全感。我肯定想的太简单了,日后的坑让未来的自己踩去吧哈哈哈。不过相比过那种能一眼望到头的生活,我更向往航行在颠簸的大海上去闯荡~

So,All in Web3, LFG!

最后

鹅厂真的就是一所社会大学,在这儿我不仅提高了技术、开阔了视野,还直面了来自企业、社会、生存等质问与挑战。一路磕磕绊绊,也幸有良师解惑、益友相伴,我真的很满意这段旅程!只是我给自己的规划到时间了,得提前下车啦。感恩在鹅厂和TME遇见的各位~

最后引用汪国真的《热爱生命》与大家共勉:

“我不去想是否能够成功。既然选择了远方,便只顾风雨兼程”

一年之计在于春,适合播种,做些新的尝试,开启人生 Round2

2022 年的春节在湖南的最后一天,迎来了一场大雪。真好,瑞雪兆丰年,既是结束,也是开始。

新的一年,我的关键词是 专注 。开始做减法,戒骄戒躁。思考自己想要的生活,大胆地做出选择,并且坚定地执行。

我想要的生活,应该是能够平衡工作与生活的独立工作者,从事有价值的事业,有时间阅读写作,日常有着些许爱好,陪伴家人与宠物,时不时出去旅游看看这个世界。 就足够了,无需大富大贵,强到没边,我只是个普通人,开始认同这一点的时候也长舒一口气。

2022,勇敢地提出辞职,同时有幸遇到 Web3.0 时代以及志同道合的伙伴们,投身 Web3.0 时代的建设,拥抱开源社区

2022 年度的目标写在这里,给自己这一年找到努力的方向:

学业

  • 复习计算机与数学基础,刷算法题,夯实学校与公司的面试要求,拿到美国大学的 offer
  • 提升英语,考出 GRE(320+)

职业

  • 与团队一起投入区块链与 Web3.0 的学习与建设
  • 学好称手兵器,编程语言以 Python、JS 为主,学习 Go, Solidity, C++。

生活

  • 规律生活,放慢节奏
  • 维持住胆囊息肉大小,实在要切除也要调养好身体
  • 坚持健身,增重至 70kg

其他

  • 培养阅读习惯,每月至少读 1 本书
  • 培养写作习惯,创立个人学习分享 IP,每周输出一篇文章或视频
  • 学习书法,每周坚持练习至少 1 次
  • 出一趟远门旅游,看看中国大好河山(新疆、西藏、甘肃选一)

回顾焦虑奔跑的 2021,我终于鼓起勇气去打破安稳的预期,追求自己未完成的出国学业梦,边工作边学托福,朝 6 晚 12 的日子想起来真的自律而热血。

然而自己的身体却开始报警,胆囊息肉已经接近临床判定要切除的大小,这让奔跑的我直接来了个急刹车。我陷入“担心工作没产出”、“不满没有个人生活”与“身体越来越多毛病”的三个焦虑构成的死循环里。

工作到底为了什么呢?思来想去,核心驱动力是赚钱,大厂平台和视野当然是很有诱惑力的驱动因素。但是我在大厂工作几年后有如下不适:

  • 业务需求与绩效压在头上,晚饭后周围同事们不愿过早下班的奇妙现象,企业微信周末也不敢不开。
  • 螺丝钉粒度的工作岗位,如果没有很确定这就是一辈子想做的领域,日子久了会感觉到束缚,要切换赛道也很有成本。
  • 工作日都是早起晚归,休息日也想着要多学些工作技能,看的书也都是技术相关的,其他方面思考与进步为零,除了工作没有其他生活的苍白感。
  • 下班后完全不想写代码,也没啥兴趣了解外面日新月异的技术浪潮。
  • 没有长假期,有钱却没时间去旅游、陪家人了。

Man, I don’t like this way. I want to live and breathe.

到底是我能力问题,还是国内互联网行业的快节奏陀飞轮,反正我找不到 work life balance 的平衡点。既然已经申请了 master,那 2022 年干脆就辞职,gap 一段时间休息一下,等 offer 的同时把失衡的身心和生活调整回来吧!

距离上一次写博客已经阔别两个多月,今天我又回来了。

这两个月发生了几件足以改变我的人生轨迹的事情,第一件是外婆受车祸离世,第二件是我工作之余冲托福一个半月冲到 99 分,第三件就是学校的申请。

10 月 29 日,一个再正常不过的周五 w 晚上,我跟同事刚吃完免费的稻香,准备买单回工位

本篇文章阅读时间:10min
读者预期的收获是:

  • 认识测试驱动开发
  • 非常简单开启你的 TDD 之旅
  • 可以编写自动化测试
  • 重构、重新设计旧的代码更加自信

引子

(压抑背景音乐渐入——)

旁白:为何深夜的办公室传来程序员的哀嚎?

为何说好的一刀 999,砍下去伤害为 0?

为何程序员好基友反目成仇,因代码调用出问题后甩锅大打出手?

当个程序员,好难!(捂着铮亮的脑门)

程序员甲:自从用了 TDD,测试驱动开发之后,每天下班早了,BUG 变少了,基友不吵了。

程序员乙丙丁:真的吗?有这么神奇吗?!(集体星星眼)

程序员甲:没错,让我来给你们安利吧!

(雪花屏)Beep——

在这里插入图片描述
Hi,我是 Bruski。开头的段子纯属瞎编,但其中描述的场景:代码不按预期执行、协作的接口不可靠等等,在我们日常工作中其实挺常见的。
原因可能千奇百怪,比如在犯困的午后工作,比如没想清楚就动手等等,而且在过程很糟糕的情况下,输出还没有自动化测试去保证,那线上在跑的程序很可能就是一颗不定时炸弹。

那有没有什么办法能最大程度避免以上情况呢?我会说,不妨试试极限编程(XP)中的优秀实践:测试驱动开发吧!
在这里插入图片描述

别问,先感受

那么到底什么是测试驱动开发呢?

别急,先来感受一道小题目,非常简单:FizzBuzz

题目模板地址: git clone https://github.com/bruceeewong/tdd-kata.git -b kick-start

题目来源:极客学院-测试驱动开发实战营


FizzBuzz 是一个简单的猜数字游戏。
在这里插入图片描述
想象你是个小学 5 年级的学生,现在还有 5 分钟就要下课,数学老师带全班同学玩一个小游戏。他会用手指挨个指向每个学生,被指着的学生就要依次报数:

第一个被指着的学生说“1”,第二个被指着的学生说“2”,如果一个学生被指着的时候,应该报的数是 3 的倍数,那么他就不能说这个数,而是要说“Fizz”。5 的倍数也不能被说出来,而是要说“Buzz”。

于是游戏开始了,老师的手指向一个个同学,他们开心地喊

着:“1!”,“2!”,“Fizz!”,“4!”,“Buzz!”……

终于,老师指向了你,时间仿佛静止,你的嘴发干,你的掌心在出汗,你仔细计算,然后终于喊出“Fizz!”。运气不错,你躲过了一劫,游戏继续进行。

为了避免在自己这儿失败,我们想了一个作弊的法子:最好能提前把整个列表打印出来,这样就知道到我这儿的时候该说什么了。

题目要求

写一个程序,打印出从 1 到 100 的数字,将其中 3 的倍数替换成“Fizz”,5 的倍数替换成“Buzz”。既能被 3 整除、又能被 5 整除的数则替换成“FizzBuzz”。

要求:

  • 代码整洁,没有重复代码
  • 有单元测试,单元测试覆盖率 100%
  • 5 分钟内完成

题目解析

相信大家应该都能很快地实现题目的要求,不过,关于单元测试部分,大家写的是否轻松呢?接下来我想给大家展示下我的做题思路——用 TDD 的方式。

测试驱动开发的要义是:测试先行,没有失败的测试,就不允许实现。所以,在动手前我们需要想清楚题目要实现什么,即拆解需求。再回顾下题目要求:

打印出从 1 到 100 的数字,将其中 3 的倍数替换成“Fizz”,5 的倍数替换成“Buzz”。既能被 3 整除、又能被 5 整除的数则替换成“FizzBuzz”。

打印出 1 到 100 的数字?也许会有人开始构思程序:一个 for 循环,if-else 一下,再 console.log 一下。等等,输出打印到控制台的话,我们怎么写测试验证输出是否正确呢?所以不妨转换下思路,沿着函数的本质:input -> process -> output来思考,其实我们要做的是:

实现一个函数

输入: 1~100 的数字

处理:

  • 3 的倍数替换成”Fizz”

  • 5 的倍数替换成“Buzz”

  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”

  • 其他数字则转换为字符串

输出:字符串

将需求完全拆解后,对应的测试用例也就信手捻来了,就让我们从最最简单的测试开始,函数就叫 fizzbuzz 吧,接收参数 1,返回字符串“1”。(这种直白的语法就叫断言(Assertion),即把预期输出与实际输出作对比以验证程序是否正确运行)

1
2
3
4
5
6
7
8
// 以下语法为Jest.js的测试写法
const fizzbuzz = require("./fizzbuzz");

describe("fizzbuzz", () => {
test("测正常数字返回", () => {
expect(fizzbuzz(1)).toEqual("1");
});
});

执行jest命令运行测试,结果不出所料地报红了:fizzbuzz is not a function,毕竟我们此时连函数都没声明。

在这里插入图片描述

那我们赶紧定义函数:

1
2
3
4
function fizzbuzz(num) {
return "1";
}
module.exports = fizzbuzz;

有人会说,函数体返回常量,你在骗自己吗?别急,再执行一下 jest 命令运行测试:
在这里插入图片描述
Yes,测试通过,变为绿色!没错我是硬编码返回了,但这是 TDD 的第二个重要的要义:只写让测试恰好通过的代码。好吧我知道留着这样的代码,是不敢入睡的,那就再多加一条测试:

1
2
3
4
5
6
7
8
const fizzbuzz = require("./fizzbuzz");

describe("fizzbuzz", () => {
test("测正常数字返回", () => {
expect(fizzbuzz(1)).toEqual("1");
expect(fizzbuzz(98)).toEqual("98");
});
});

再运行测试,闭着眼睛都知道会失败,自动化的测试还非常贴心地帮我们指出了期待输出和实际输出的差异。
在这里插入图片描述
有了失败的测试,我们才开始动手写实现,实现也相当简单:

1
2
3
function fizzbuzz(num) {
return num.toString();
}

执行测试,OK,测试通过,结果又变回绿色。
在这里插入图片描述
这时候我们知道第一条需求已经被解决,无情划掉它:

  • 3 的倍数替换成”Fizz”
  • 5 的倍数替换成“Buzz”
  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”
  • 其他数字则转换为字符串

那就写下第二条测试用例:

1
2
3
test("测3的倍数返回", () => {
expect(fizzbuzz(3)).toEqual("Fizz");
});

执行测试,结果 3 原样返回,测试不通过:
在这里插入图片描述
又是一条失败的测试,快速实现它让它翻绿!

1
2
3
4
5
6
function fizzbuzz(num) {
if (num % 3 === 0) {
return "Fizz";
}
return num.toString();
}

再次运行测试:
在这里插入图片描述
那此时再加几条测试,结果肯定是正确的:

1
2
3
4
5
test("测3的倍数返回", () => {
expect(fizzbuzz(3)).toEqual("Fizz");
expect(fizzbuzz(6)).toEqual("Fizz");
expect(fizzbuzz(99)).toEqual("Fizz");
});

再划掉一项需求!

  • 3 的倍数替换成”Fizz”
  • 5 的倍数替换成“Buzz”
  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”
  • 其他数字则转换为字符串

接下来想必大家都知道了,复制一下 3 的测试用例,改成 5,然后执行测试,失败,然后复制一条 if 语句….等等!难道你忘了,Copy-Paste 是魔鬼吗?难道我是在教你成为一名 CV 工程师吗?好了,这里引出 TDD 又一条要义:消除所有重复。其实通过将运算抽象为函数,很容易就能消除重复的直白代码:

1
2
3
4
5
6
7
8
9
10
11
12
function fizzbuzz(num) {
function canDivideBy(num, divideNum) {
return num % divideNum === 0;
}
if (canDivideBy(num, 3)) {
return "Fizz";
}
if (canDivideBy(num, 5)) {
return "Buzz";
}
return num.toString();
}

运行测试,干净漂亮地通过了测试:
在这里插入图片描述
最后再补充一条 3 和 5 的公倍数测试用例,使用抽象好的函数实现,运行测试,测试通过后,那么整个需求就完成了。下面是完整的测试用例&实现&测试截图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// fizzbuzz.test.js
const fizzbuzz = require("./fizzbuzz");

describe("fizzbuzz", () => {
test("测正常数字返回", () => {
expect(fizzbuzz(1)).toEqual("1");
expect(fizzbuzz(2)).toEqual("2");
expect(fizzbuzz(98)).toEqual("98");
});
test("测3的倍数返回", () => {
expect(fizzbuzz(3)).toEqual("Fizz");
expect(fizzbuzz(6)).toEqual("Fizz");
expect(fizzbuzz(99)).toEqual("Fizz");
});
test("测5的倍数返回", () => {
expect(fizzbuzz(5)).toEqual("Buzz");
expect(fizzbuzz(10)).toEqual("Buzz");
});
test("测3和5的公倍数返回", () => {
expect(fizzbuzz(15)).toEqual("FizzBuzz");
expect(fizzbuzz(45)).toEqual("FizzBuzz");
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// fizzbuzz.js 实现
function fizzbuzz(num) {
function canDivideBy(num, divideNum) {
return num % divideNum === 0;
}
if (canDivideBy(num, 15)) {
return "FizzBuzz";
}
if (canDivideBy(num, 3)) {
return "Fizz";
}
if (canDivideBy(num, 5)) {
return "Buzz";
}
return num.toString();
}

module.exports = fizzbuzz;

在这里插入图片描述
划掉所有需求,爽,可以下班了!

  • 3 的倍数替换成”Fizz”
  • 5 的倍数替换成“Buzz”
  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”
  • 其他数字则转换为字符串

最后,执行 Jest 命令jest --coverage生成测试覆盖率报告:
在这里插入图片描述
怎么样?100%的测试覆盖率,没有重复、多余的代码,漂亮地完成所有需求。如果你不放心,多加几条测试用例,多运行几遍测试命令,这就是测试驱动开发产出的有质量保证的代码。

有了自动化测试做保障,测试通过,我就敢说在我所预见的情况中,他会一直通过,除非,除非产品经理的需求又变了…
在这里插入图片描述
总结一下,在做 FizzBuzz 题目的过程中,用 TDD 的节奏开发流程如下图:执行测试覆盖率检查

如果大家有关注到图中的颜色,那么请大家跟我念一句 TDD 的咒语,念三遍:

Red / Green / Refactor

Red / Green / Refactor

Red / Green / Refactor
在这里插入图片描述
这就是贯穿测试驱动开发的整个流程的循环,也是 TDD 的节奏。其中:

  • Red表示测试不通过时,IDE 的状态条报红,此时我们有了失败的测试;
  • Green表示测试通过时,IDE 的状态条回到绿色状态,此时通过快速小步地开发,让测试恰好通过;
  • Refactor表示重构代码,消除所有重复的逻辑。

接下来,让我们跟随 Kent 大叔深入地琢磨下测试驱动开发吧!

深入测试驱动开发

到底什么是测试驱动开发(Test-driven Development)呢?

按照 Kent 大叔的原话:

TDD is an awareness of the gap between decision and feedback during programming, and techniques to control that gap.

粗浅地翻译过来意思是:

TDD 是一种编程意识,关注着程序设计与实现结果反馈之间的间隙;同时 TDD 也是用以填平该间隙的一系列技巧。

是的,概念总是过于晦涩与抽象。作者还提供了不同角度的定义来帮助理解:

  • 测试驱动开发是一种管理编程中的恐惧的方式。(Test-driven development is a way of managing fear during programming. )
  • 测试驱动开发是一种开发风格:我们通过自动化的测试来驱动开发(we drive development with automated tests, a style of development called Test-Driven Development)

你一定要记住,TDD 的终极目标是:

Clean code that works. (产出干净且可用的代码)

这是《测试驱动开发》序章的第一句话,也是我编程的座右铭。

TDD 开发模式

首先我们要搞清楚 3 个问题:

  1. 什么是测试?
  2. 测什么?
  3. 什么时候测试?

什么是测试

测试作为动词,是“去验证”的意思。测试作为名词,是对预期得出可接受或者不可接受判断的一个过程

按 Kent 大叔的意思是:

测试是开发者的基石,也是将对程序运行结果从未知的恐惧转化为熟知的手段。

不喜欢写测试的程序员,通常将经历这样的消极循环:感到恐惧,因为没测试 -> 越恐惧,压力越大 -> 压力越大,越不会测试

而善用测试的程序员呢?正向循环应该像这样:越感到恐惧,越执行测试 -> 越测试,恐惧越小 -> 压力越小,越愿意测试

测什么

我们这里指的,是程序级别的单元测试(Program Level),主要关注逻辑数据

对于逻辑的测试,一般来说等同于需求,我们要对需求进行编程级的拆解,即要能拆解为可以动手编码的若干步骤,通过不断地写下你的期望与实际输出的测试语句(即断言),然后实现代码让其通过,从而一步步达成目的。

对于数据的测试,这里我也没有很多实践,有几点可以分享:

  1. 不要使用真实的数据(数据库数据、网络请求等)
  2. 按照预期的数据结构,构造直观的伪造数据来满足测试。

什么时候测试

按照测试驱动开发的节奏,每当:

  • 动手编程前,先写出一条会失败的测试
  • 重构前,保证测试通过

了解完前置概念后,又该怎么落笔我们的第一个测试用例?

Red Bar Patterns

Red Bar,顾名思义:因执行测试失败而显示红色的状态栏

要让测试失败,那首先要写下你的测试,我们上一节介绍了需求拆分,得到了一份 todo-list,那我们究竟该自顶向下实现(即从大问题的具体用例开始实现),还是自底向上实现(从小模块开始,再逐步聚合)呢?

其实这两者都不能很好描述 TDD 的实现过程,准确地说,实现顺序没有太大关系,因为 TDD 是基于 known-to-unknown 的模式进行,即用已知的来推出未知的。我们在拆分需求为一条条可编程验证的用例时,就是将未知的庞然大物拆解成不废力气就能达成的小目标,我们知道如果一步步实现了所有子测试,最终需求就能实现。

在 TDD 这里,万事开头难,但测试开头易。第一个测试应该写一条测什么都不做的操作的测试,这里看似没什么意义,但是它确实验证了:

  • 这个操作属于哪里?
  • 什么是正确的输入?
  • 什么是基于正确输入的正确输出?

Green Bar Patterns

Green Bar,顾名思义:因执行测试成功而显示绿色的状态栏

在 FizzBuzz 实现的过程中,我们用到了几种快速让测试通过的技巧,分别是:

Fake It (‘Til You Make It) 伪造数据

比如在 FizzBuzz 最开始的时候,为了让测试通过,直接在函数里返回常量。

为什么要写早晚要换掉的实现?原因有两点:

  • 心理暗示
    • 测试成功比测试失败好
  • 范围控制
    • 专注在解决当前测试上,避免过度设计
    • 保证当前代码始终可用

Triangulate 三角测量

  • 从不同角度测试代码,让伪造数据的代码失败,然后抽象、实现,让测试通过。例如我们前面用两条测试,宣告了硬编码返回”1”的代码实现的死亡。

Obvious Implementation 最简实现

  • 既然用例已经拆分成小步,一定可以快速实现,否则,反思步子是否迈大。

  • 写恰好实现的代码。

至此,结合 FizzBuzz 的解析,我们已经体验完测试驱动开发最核心的流程。

来总结下吧

再回到定义,测试驱动开发本质上是一种编程思考和实践的一种风格/方式,比起一开始的顶层设计,他更关注需求与实现之间的距离,要求程序员能拆解成若干可测试、可实现的步骤,然后借助自动化测试工具,按照一定的节奏Red / Green / Refactor、测试技巧和编程手法,从而产出干净的、可工作的代码。

  • 因为测试先行,倒逼我们必须思考清楚问题应该如何解决,避免了低效地走一步看一步的浑浑噩噩;
  • 因为测试先行,我知道做到什么程度算完成,并且自信地认为在我所预期的情况内,程序可以良好地工作。
  • 测试用例可以作为更棒的注释而存在,让协作的同事更清楚地知道函数的用途和用法。
  • 提交代码时,看着绿色的状态栏,心情愉悦,安心下班!

TDD 的挑战

TDD 更多的应用在程序级别的单元测试,这一块是开发人员完全自主掌控的部分。而在此之外的一些场景,TDD 也许就不那么合适,比如:

  • 对于 GUI 的测试(网页、App 级别的 UI 测试)
  • 对于依赖数据库的测试(通常我们使用 mock 对象测试)
  • 不要去测第三方的代码,那应该有他们的开发去保证(如框架等)
  • 不能测试编译器之类的东西。

写在最后

作为一名 Web 前端开发,在开发业务逻辑时,我都会有意识地使用 TDD 的方式来实现。(UI 方面的测试实践并不多,还要继续学习!)
TDD 测试驱动开发带给我的开发体验是:

  1. 享受可预测、尽在掌握的开发体验
    1. 当通过了所有测试、开发也就结束了
    2. 并且开发结束了,可预见的场景不会有太多 bug
  2. 给自己留一瓶后悔药
    1. 第一次的实现可以很烂,但只要有测试,再回过头来,只要测试是通过的,就可以放心地重构。
    2. 如果祖传代码没有测试,那就尝试找到程序输入输出的接缝处,给他补充测试,这样可以最大程度确保重构不会大刀阔斧地破坏原有逻辑。
  3. 保持代码年轻的秘诀?
    1. 如果是测试驱动出来的代码,将拥有输入输出清晰、幂等性特点。
    2. 每当添加新特性前,先思考清楚,先写测试,代码不会随乱涂乱改而腐败。
  4. 同事协作时之间更放心
    1. 你产出的代码值得信赖。
    2. 同事也用 TDD,看着测试用例就知道怎么用了,真香。

这篇文章只是展示 TDD 的基础玩法,想要深入了解测试驱动开发,去读下 Kent Beck 的 《Test-Driven Development By Example》,感受 Kent 大叔的幽默与智慧吧。
在这里插入图片描述
愿你的程序无 Bug,早点下班!
在这里插入图片描述