您现在的位置是:KOK球盘体育 > 单元作文 >

开发需要写单元测试吗?

2020-04-30 14:05单元作文 人已围观

简介即使恨也要再爱一次网上看到有个吐槽ThoughtWorks面试的帖子,说因为单元测试写的不好所以被拒。 而我目前所在的单位,是没有单元测试的。而且好像从网上看,好多初创公司(小公司)基本上也都没有要...

  网上看到有个吐槽ThoughtWorks面试的帖子,说因为单元测试写的不好所以被拒。 而我目前所在的单位,是没有单元测试的。而且好像从网上看,好多初创公司(小公司)基本上也都没有要求单元测试,基本上就是开发做完了,直接扔给测试,然后好多测试也就是点点功能,也就算测完了。 所以很疑惑,单元测试是必须的吗?好处在哪里? @vczh 召唤大神,等待解惑

  首先,他们的产品fail了影响不大。尤其是网页开发,网页崩了,显示对不齐了,移动端看不见内容了,这根本不是个事儿。用户早就习惯了网页不完全按照他们理想的状态工作了。因此对于这些公司来说,有必要把精力放在大规模的测试上么?完全没有。试试新功能在自己的浏览器下好不好使就可以上线了,上去了有bug大家也不会在意。

  其次,他们的产品更新速度快。更新速度是个双刃剑,比如对于网页端的开发来说,更新产品难度极低,发现了bug可以随时修复。同时更新的feature比更稳定的工作环境对于他们的产品更重要。所以很多人觉得自己写代码就是在写bug,修复了之前的一些bug,把更多的bug跟着新的feature加进去,未来再修复。反正发现了可以随时修复嘛。这里说一句题外话,Tesla的firmware update想法非常牛,节省了海量召回成本。但是在固件如此容易更新的情况下,是否Tesla会降低对出厂产品稳定性的要求,从而让新的feature更快地面世?不得不说是个隐患。

  第三,他们穷。当你的钱只够开发或者测试的时候,你选什么?对于大部分小企业来说这是实实在在的问题。他们恨不得雇一个码农来实现他们伟大的想法,把码农当工地的工人,自己是设计高楼大厦的建筑师。最后的结果当然是码农只有经历做feature开发,而没有精力去做大规模测试。

  我觉得比较合理的方法应该是开发自己写一份初稿,然后测试去完善。为什么初稿要开发去写?因为开发者对自己的程序了解程度是最深的,他最清楚自己的程序在哪些corner case容易出错,也最了解哪些feature应该被测试。如果写完直接扔给测试,那测试要从头理解他的代码,很可能还理解上有偏差,完全是事倍功半。

  但是如果让开发去完整地写一个test,第一浪费开发的时间,第二每个开发对测试系统的了解也不够透彻,很可能大家的test长得千奇百怪的。这个部分恰好是测试擅长的。当测试知道了这个单元的大致测试方向,他可以更好地对接测试系统,把测试写得高效,同时有有效的log信息。

  单元测试这个东西,还是要看你以什么为一个「单元」。对于大部分公司来说,对每一个函数进行「单元测试」的价值都是偏低的。但是如果你有一个比较独立的功能(当然也可能这个功能就一个函数),应该对这个功能进行比较详尽的测试。

  当然也要看你对产品的稳定性需求。比如Compiler这种东西,那是真的每个标准函数都要写单元测试的。。

  卖东西需要提供(法定之外的)保修吗?那要看保修对买家购买选择的影响力有多大啊。

  买东西需要选择买有保修的吗?那要看这个东西有多大的可能性会坏以及坏了的情况会有多糟糕。

  测试也是同样的道理,说不清楚写测试的成本有多高以及不写测试的后果有多坏,那是无法通过对比得到答案的。一切关于测试有多好的高谈阔论,都是空对空。

  具体到某一家公司,写不写测试可能是由文化决定的,这个文化可能是由这家公司所在的业务领域经过长时间积累而形成。如果一家公司所在的领域犯错成本很高,一番自然淘汰后,省下来的都是通过测试以及其它手段保证质量的公司。但如果一家公司所在的领域犯错成本很低,那自然是「move fast and break things」了。

  在这方面我可以轻易地使用 Facebook 作为例子,反对@程墨Morgan和@vczh的答案。Facebook 就是长期运作在一个犯错成本很低的领域,出了什么问题就道个歉啊,然后说自己真心想做好事的。就算是国会传唤,也是同样的套路,道歉了事。

  监管部门要罚款?罚金数额只是 Facebook 现金流的一小部分,没关系。换一个医疗或金融行业这样做试试看?这些行业在美国的监管如此之强,随便犯个小错就可能罚到你破产。

  题主去 ThoughtWorks 面试因为不重视测试而被鄙视了,但不能因此就说越是大型和正规的公司就越重视测试。ThoughtWorks 是做咨询的,客户企业可能从事受到严格法律监管的领域,要求高也是可以理解的,但 Facebook 显然是个反例。

  我曾经在 Facebook 做了那么多年的 reliability,追踪调试了多少 production errors。不仅仅如此,有多少人因为「浪费时间写测试」而在绩效讨论时得负面反馈的,很多人最终都学会了「只做有价值的事情」,如果不能说明白测试的价值就不写测试,如果能把测试的价值吹上天那写得越多越好。

  最后我就推荐一本书吧,叫做《Antifragile》,中文版叫做《反脆弱》:

  如果你的软件是脆弱的,也就是出现随机波动的负面影响远大于正面影响,那你就乖乖写测试吧,你的目标是让你的软件变得健壮。

  发明新产品必须是一个反脆弱的过程,也就是说软件波动起来正面影响要远大于负面影响,这时候还写什么测试,赶紧让软件在有限的时间内尝试尽可能多的不同的改动,看看哪一个能成功爆发。

  为什么这是个反脆弱的过程呢?因为如果软件的任何一个尝试都不成功,你的损失是有限的,你原本没有一个成功的软件,现在还是没有一个成功的软件,时间损耗是可控的。但如果任何一个尝试成功了,那都可能是指数级爆发,那是不封顶的。

  但如果你把 Fabrice 拿来当自己不写测试的借口,先估摸下你和他之间差了几个轮子哥吧……

  单元测试最大的好处就是这种测试的“单元性”。你想想如果你不写单元测试,然后你又给程序增加了一个新的模块(比如说一个函数也可以算一个模块),这个时候怎么知道写对没呢?肯定会把程序跑起来,然后操作一下看看。

  。系统测试毫无疑问是必须的,但如果靠它来排查错误,是非常耗费时间的。主要原因,一是测试代价大,必须把各个模块合起来成为一个系统启动后才能测试;二是测试覆盖难,要想测试某个模块被调用时的各种情形,难度很大,因为从用户操作到最终触发某个模块之间,调用链条可能很长,或者模拟起来代价特别大。

  你不需要启动整个系统,就可以直接的,针对性的对任意模块进行测试。而且可以简单的模拟各种情况覆盖其各种分支。

  我们这个行业里很多弔诡的事,可以缩影到这一件:所有人都赞同单元测试非常重要,然而很少人做单元测试。

  有很多公司非常准确地把单元测试叫做“开发自测”,并且非常准确地认识到了这个活动的重要性:程序员只要认真测一测自己写的代码,bug就能减少90%。至于时下流行的敏捷么,我毫不夸张说一句:一切没有充分单元测试覆盖的敏捷都是伪敏捷。别的啥都不说了,没有充分的单元测试覆盖,你持续集成跑啥?持续集成不能保障软件质量,靠测试人员跟在屁股后面人肉回归么?

  但是就这么一件所有人认可其价值、所有人都重视的事,我们这个行业里,我客气点说,80%的企业落不了地。这难道不是一个值得玩味的文化现象么?

  软件工程教材说,代码应该有单元测试。这没错,但这话没说完,因为它只描述了一个结果状态。怎么去到这个结果状态的过程,它没有说。什么时候写单元测试?怎么写?单元测试和产品代码之间怎么配合?什么时候运行单元测试?这些每天工作的细节,教材里全都没有。

  实践者们看了这个,能知道怎么动手写单元测试么?于是大家就只好靠猜。想必一定是先写好产品代码再写单元测试来测它吧?一定是这样的!卡桑,我这就可以报效祖国去了吧!

  然后一写发现满不是这么回事。因为在写代码的时候并没有考虑这代码要怎么测,所以写完了以后要测发现很难,找不到接缝,测不动。这时候交付压力又紧逼着,唉,要不先放着改天再测吧。当然我们都知道,这个改天再做的事就再也不会做。

  我们这个行业里有很多事都类似这样。有一些大师告诉你,这事应该怎么怎么做。但是他其实并没有告诉你“怎么做”,而是告诉你“做成了之后是什么样”。他给你的是一个结果状态,而不是怎么达到这个结果的过程。

  你是不是想起了某头文字A的著名公司,他们给的每个方案都是一大堆框框图,告诉你“一年后系统建成了就长这样”,至于怎么建、按什么顺序建、用什么方法建,他们不怎么讲。(这也就是为什么他们的每个框框图长得都差不多。)

  再比如DDD,为什么大家看别人做的领域建模都觉得特别有道理,一到自己建模就不知道咋下手呢?因为DDD这本书讲的是结果状态,是一套用于“描述已经建好的模型”的语言。真正讲“如何建模”的,是Color UML这本书。

  所以这其实是一个鉴别面前的专家到底是不是真懂行的办法:你看他讲的是结果状态,还是逐步逼近这个结果的过程。如果他只给你画一堆框框图说你这事干成了之后是这样,那基本上就可以知道,他对于“怎么干成”这个过程,没啥想法。

  那说回单元测试来。软件工程教材里没讲的那一半,单元测试怎么能落地的过程,到底是怎么回事呢?

  单元测试落不了地——以及后面衍生的持续集成没有用、用户故事拆不小、每日站会没话说等等一系列问题——其实都是一个源头:没有TDD。

  简单/无脑的部分你再去测就是秀智商了,所以默认你说的是有一定复杂性的独立单元。

  很有意思的一点是,实际上开发写的单元测试很大程度上是利己的,就是,让自己更舒服。但很多人却曲解为,浪费时间、麻烦、没用、便宜测试那小子了等等。。

  2. 帮助了解需求说明,毕竟单元测试能写出来就说明你了解你这块儿的输入/输出,使用场景

  这是不是避免了漏掉功能/多写了没用的功能?不多干,不少干,人生又轻松了很多

  3. 帮助构思功能模块代码。边写代码边写测试,构思测试的过程(怎么验证等等)让你对需要实现的逻辑更加清晰,而且每一时刻只关注某一个具体的部分。

  4. 方便回归。改了实现只要自己写的测试(没问题)能过,那就基本没啥问题

  但是,有一点,这儿的“单元测试”可不是非让你规规整整地,跟写作文儿那样搞个测试报告那样,也不是非得让你把每一个corner case都覆盖到,这都是测试的活。这里我们追求的是效率-可靠的trade off。用最少的测试,来赢得最高的后续效率和可靠性。既不会写太多的测试代码,拖累实际开发,又能够规避大部分的基本错误,侧面辅助自己的高效开发。

  那么如何做到这样的单元测试呢?嘿嘿,这又是另一回事儿了,TDD的最佳实践(此处应当@vczh),还有使用测试的经验,都需要时间来体会。

  总的来说,如果你能正确使用开发中的单元测试,舒适度简直++(test pass的时候emm)。如果你的场景代码没啥要求,写了就扔那种,当我啥都没说。

  1. 公用组件库,SDK,所有公共开发的基础部分,都需要有严格的单元测试保证,而且这些东西变更不会特别频繁,所以覆盖率需要100%。

  2. 业务项目,需要自动化接口功能测试,不需要单元测试,一个是成本太高,而是变化太快,并无必要。(PS:没有测试工程师的团队除外)另外,业务feature代码,都要经过师兄的详细 review!加上测试工程师的全功能回归,一定程度上代替了单元测试的功能。

  单测要因人而异!不要为了听起来高大上就要做单测!最终目的就是保证项目质量,而不是炫技,毕竟单测并没有什么技术含量!即使恨也要再爱一次

  关于单元测试是不是『需要』的,前人已经说了很多了,我也说了很多了,所以不如我们换一个角度,用一个故事来说明到底是『需要』的还是『不需要』的。

  一个印度人,他在印度的时候从来都是拿手抓饭的,然后,他听说中国生活好医疗条件好,就来中国了,中国人吃饭都是用筷子的,部分中国人用刀叉的,中国人看到印度人吃饭用手,就告诉他,最好入乡随俗,学着用餐具吃饭。

  中国人:吃饭用手不卫生,你吃之前也不洗手,为了你自己的健康,最好用餐具。

  中国人:你有你的自由,我们有保证自己身体健康的自由,和你这样不洗手就用手吃饭的人一起吃饭,我们觉得不健康。

  印度人:以前不写是在弱鸡公司凑合,后来发现不写我找不到工作啊,高大上的公司都要求写单元测试的。

  印度人:我哪敢?不按照他们的标准来,他们不招我,我在网上放P也没人听啊。

  中国人:洗手不洗手、拿不拿手吃饭不是一个道理吗?你在印度不洗手拿手吃饭,就和在印度生活一样,你现在来现代文明世界,就该用餐具了,不然别人不接受你,你TMD还能怪别人了吗?不同环境下,就应该用不同的方法!

  印度人:反正我就是习惯了用手吃饭,呵呵,告诉你吧,我写的那些单元测试,也都是凑合,根本没怎么测。

  单元测试应该是程序员的必备技能,而真正的编程高手应该善于把握单元测试的粒度。

  在前一篇博客,我提及到了最近在对后端Node.js服务进行代码重构,将Promise替换成Async/Await。这是一件痛并快乐着的事。

  当任务完成50%之后,我发现,与其说是重构,更准确的说法或许是重写。一方面,换用Async/Await本身就意味着需要修改每个异步函数,而后端绝大多数函数都是异步的;另一方面,作为一个有着强迫症的完美主义者,我写了大量单元测试对代码进行了一系列优化,同时修复了一些BUG,并且实现了一个新功能。

  这里的关键词是单元测试,那么问题来了,重构代码就得了,写什么单元测试啊?这不是没事找事么,要知道单元测试似乎比功能代码更难写。

  这样的定义非常通俗易懂,但并不是很准确,严格来说应该是错误的。因为对API测试时,会涉及到多个函数,很多时候还会依赖于数据库、缓存以及第三方服务等外部资源。因此,API测试应该属于集成测试而非单元测试。

  根据《JavaScript有这几种测试分类》,集成测试与单元测试应该是这样区分的:

  单元测试指的是测试小的代码块,通常指的是独立测试单个函数。如果某个测试依赖于一些外部资源,比如网络或者数据库,那它就不是单元测试。

  集成测试就是测试应用中不同模块如何集成,如何一起工作,这和它的名字一致。集成测试与单元测试相似,但是它们也有很大的不同:单元测试是测试每个独立的模块,而集成测试恰好相反。比如,当测试需要访问数据库的代码时,单元测试不会真的去访问数据库,而集成测试则会。

  但是,在实际操作中,测试单个函数时,很难保证所谓的独立测试。一些函数难免依赖于其他函数、数据库、函数以及第三方服务等外部资源,这个我们很难避免,甚至有时恰恰需要验证这些外部资源。比如,验证写入数据库或者缓存的数据是否符合预期;验证数据库或者缓存中的数据对函数行为的影响是否符合预期。

  在我看来,对单个函数进行非独立的测试,不妨也可以视作“单元测试”。简单地说,本文所讨论的单元测试,就是对单个函数进行测试。

  新功能的增加,代码复杂性的提高,优化代码的需要,或新技术的出现都会导致重构代码的需求。在没有写单元测试的情况下,对代码进行大规模修改,是一件不敢想象的事情,因为写错的概率实在太大了。

  我一直在鼓励大家写单元测试,然而,有时难免偷懒。当我打算重构代码的时候,发现写的单元测试其实是不够的,这就比较尴尬了:(

  那我到底是直接改代码;还是先写单元测试,然后再改代码呢?这是一个艰难的决定,因为前者很难保证正确性,后者貌似需要耗费大量时间。

  有一种智慧叫做“摸着石头过河”:我尝试在修改函数代码之前,补写一些单元测试。这个过程并没有想象中那么痛苦,也许是因为做决定本身其实比做事情更痛苦,或者是因为我比较喜欢敲代码。

  于是,我就可以开始大刀阔斧地进行重构了:换用Async/Await;优化代码组织;优化程序性能;写新功能…忙得不亦乐乎。

  如果没写单元测试,我会改得那么快吗?当然不会!大概每改一个函数都会想半天,改完然后祈祷它不会出错;修改某个函数并不是一蹴而就的事情,如果每次修改都去磨叽半天,大概我现在还在敲代码而不是在写博客。

  正是因为有了单元测试做保证,改起来才会得心应手,效率更高。这样,既可以保证正确性,又可以节省时间。想象中单元测试会浪费不少时间,事实上似乎并非如此。

  也许大多数人没有我这么喜欢折腾,不会一直去重构代码,这种情况下,难道就不用写单元测试啦?

  另外,单元测试能够提供另一个思考代码的角度,这对于编写高质量的代码是很有好处的。

  本文聊的单元测试是针对每一个函数的,那么,你在写单元测试的时候,就会去考虑合理地拆分与合并函数。因为函数的功能区分不清楚的话,是不太好写单元测试的。

  敲代码的时候,我们考虑的是函数实现,不管三七二十一,写好了就大功告成了。写测试的时候,我们跳出了函数,从输入输出的角度去思考函数的功能,这时候,你就会去想,这个函数真的需要吗?这个函数的功能是不是可以简化一下?这个函数考虑的情况似乎不够全面吧?这些思考,可以帮助我们写出更好的代码。

  如果你是编程高手,似乎可以少写一些单元测试。王垠大神在《测试的道理》中是这样说的:

  在我心目中,代码本身的地位大大的高于测试。我不忽视测试,但我不会本末倒置,过分强调测试,我并不推崇测试驱动开发(TDD)。我知道该测试什么,不该测试什么,什么时候该写测试,什么时候不该写,什么时候应该推迟测试,什么时候完全不需要测试。因为这个原因,再加上高强的编程能力,我多次完成别人认为在短时间不可能完成的任务,并且制造出质量非常高的代码。

  那么问题来了,你是高手吗?根据二八原理,大部分开发者并非高手。在下自认为编程水平还不错,也选择尽量写单元测试。

  假设你是高手,那你能保证你的团队都是高手吗?根据二八原理,一个团队里面只有少数人是高手。如果你不写足够的单元测试,他们乱改你的代码,是会出事情的。

  另外,单元测试写得越多,其边际收益是在不断降低,是得不偿失的。神奇的二八原理告诉我们,20%的测试可以覆盖80%的问题;而剩下20%的问题,你需要写80%的单元测试。换句话说,单元测试并不能消除所有问题。因此,对生产代码进行实时错误监控是非常有必要的,这也是我们Fundebug努力在做的事情。

  这是每一个程序员都应该认真思考的问题,没有所谓的标准答案。从小接受中庸之道和唯物主义辩证法熏陶的我们,应该可以在实践当中思考合适的测试粒度。当你学会了思考,你才能成为真正的高手。

  单元测试就是一个已经经过校准的精密机床,你当然可以说纯手工的打造一个零件,但当系统足够复杂的时候,没有校准,你的东西会组装不到一起去。

  出了问题的时候,你不知道是因为哪个零件不准确造成的。唯一的办法就是挨个检查。

  但系统足够小,没有模块化,单元测试你是不需要的,因为系统本身就可以看作是一个单元测试。

  真的认真TDD的话 可能三分之二的时间都在写测试 而且大部分测试代码还没啥营养

  基本上是这样子的从第一家公司到最后一家公司,我没有任何一家公司可以做到单元测试覆盖率到20%的情况。

  第一家公司刚过去的时候,业务已经成型。测试代码只有大概100行左右,占到整个业务代码都不到0.1%,所以即使我想用测试的方法也没有什么意义。

  我虽然一直觉得测试是非常重要的,我觉得测试和生产代码的比例达到1:1的比例比较合理的。但这即使在一个从0到1的产品开发过程中也依然没有推行下去。

  最开始使用clojure的时候,大家习惯了使用repl的方式来做开发,再加上稍微使用一下宏就可以打印出很多调试信息。这个比写测试代码“省时省力”多了。这种环境中基本很难让大家形成需要写测试代码的习惯。大家的基本想法仅仅是完成现有业务而已。同时又因为没有一个非常成熟的业内实践,大部分有副作用的函数都是与数据库相关的。这个需要较为合理的加载测试数据,mock测试函数,事务回滚机制。最头疼的事情是系统依赖第三方支付。简单一个事例,用户开户操作不能成功执行两次,第一次返回成功,第二次就会异常,返回重复开户。导致单元测试基本属于形同虚设。

  最近的项目使用就spring框架,有junit+mockio,整个测试的生态都非常完备,而且又是从0开始的项目,理应可以推行好单元测试。但即使是这样,依然没有,把测试覆盖率达到20%。在项目开始时由于交付时间的压力,导致大家都有些偷懒。整个技术团队如果不实行单元测试,那么基本就没有办法开展了。比如,前端开发不做单测,后端做,会导致项目进度压在后端身上,同理。

  大家在追求“快速交付”的前提下,代码质量这件事情就推脱给了QA。这件事情的严重后果就是没有人对这个系统有足够的把握它会运行正常。

  后期时间稍微充裕的时候,我提倡大家在团队内部做单元测试的补充,尽量覆盖到之前的代码代码,基本没有人会实行。

  1.单元测试应该配合代码审查在团队中,有人没有在做充足的单元测试的时候可以拒绝审查。

Tags:

本栏推荐

标签云

站点信息

  • 文章统计3582篇文章
  • 标签管理标签云
  • 微信公众号:扫描二维码,关注我们