青眼看测试

阅读数:6754 2019 年 6 月 14 日

男女有别,尽管许多国家不断地采取措施提高女性的地位,男女不平等至今还是全世界的难题。同样地,一个公司无论怎样努力地标榜对测试的重视,也难以抚慰测试工程师在和开发工程师比较时那种青涩、暧昧、欲语还休的复杂心绪。坦率且勇敢地面对现实,不妨就直说了吧,测试和开发天生存在不可逾越的鸿沟吗?测试工程师注定低开发工程师一头吗?

稍安勿躁,让我们从测试工程师和开发工程师共同工作的对象程序的构造说起吧。众所周知,构成程序的两大要素是过程 (Procedure) 和数据 (Data),正如 MIT 计算科学教材 SICP (Structure and Interpretation of Computer Programs) 开篇所讲的那样,程序是抽象的精灵。

“Computational processes are abstract beings that inhabit computers. As they evolve, processes manipulate other abstract things called data.”

因“四人帮“(Gang of Four) 的著作 (Design Patterns: Elements of Reusable Object-Oriented Software) 而名噪一时的软件设计模式 (Software Design Pattern) 系统地描述了解决一般性问题的可复制的编码 (programming) 参考模板,它本身就是最佳开发实践的抽象。而经典的模式如简单工厂模式 (Simple Factory)、工厂方法模式 (Factory Method)、抽象工厂模式 (Abstract Factory)、建造者模式 (Builder)、适配器模式 (Adapter)、策略模式 (Strategy) 等背后的共同原理不过是抽象在程序设计中的具体应用。

抽象并没有想象中那样的抽象。简单地讲,名正则言顺,给程序的过程 (方法和函数) 或数据 (类、对象或变量) 赋予一个名字,则其他的过程或数据通过引用该名字而重用该过程或数据,这其实就是抽象了。就程序设计而言,隔离是抽象相伴而生的孪生兄弟,仿佛一枚硬币的正反两面,有抽象就有隔离,反之亦然。抽象出来的是程序中一般的、可重用的的过程,而其余的处理千万种特定情况的过程就是要隔离的过程。过程如此,数据亦然,比如,类的组合与继承就是数据的抽象和隔离。我们引用 SICP 中的一个平凡的 (Trivial)scheme 的例子来说明这种抽象与隔离的艺术:

复制代码
(define (map proc items)
(if (null? items)
nil
(cons (proc (car items))
(map proc (cdr items)))))
(define (scale-list items factor)
map (lambda (x) (* x factor))
Items))
(scale-list (list 1 2 3 4) 10)
结果是:(10 20 30 40)
(map (lambda (x) (* x x))
(list 1 2 3 4))
结果是:(1 4 9 16)

map 完成了 list 的遍历和再组装,这是 list 处理的一般的和可重用的过程,即为可抽象的部分。对 list 的元素的转换却有千万种情况,而这种不同被隔离在了传递给 map 的过程参数 ((lambda (x) (* x factor)) 和 (lambda (x) (* x x))) 中。作为参数传递的过程扮演着数据的角色,这正是所谓的函数型编程语言 (Functional Programming Language) 如 scheme 再平常不过的抽象机制。抽象和隔离给程序设计带来了极大的灵活性。比如说,将 list 中的数值元素转换为它的绝对值,并不需要重新写一个过程,遍历 list 的每个元素,算出绝对值,并把转换后的元素组成新的 list。取而代之的是仅仅把取绝对值的过程作为参数传递给 map,这是何等的简洁便当:

复制代码
(define (abs-list items factor)
map (lambda (x) (if (< x 0)
(- x)
x))
Items))

抽象不是为了抽象而抽象,耍酷或故作高深更不是抽象的目的。抽象是克服程序复杂性的一种计算思维方法,由抽象定义的类似软件设计模式那样的结构可以把代码以可理解的方式有效地组织起来,来保证代码的可读性,降低代码维护的成本。否则,一切混乱的象一堆麦秸,开发百万行乃至于千万行代码的大型软件系统从项目的角度是无法管控的。从纵向观察,由抽象带来的分层描述了程序最一般的结构。比如,软件低层的基础设施隐藏了实现的细节,向上层的功能应用提供了透明的抽象接口。因此,层在程序的不同组成模块建立了抽象屏障,在接口不变的情况下,低层代码的改变不会影响上层代码的逻辑和行为。众所周知,在互联网工业界,软硬件建立在一个经典的分层模型基础之上,即开放式系统互联通信参考模型 (Open Systems Interconnection model) 和相关的 TCP/IP 协议簇 (TCP/IP Protocol Suite)。若没有定义在该模型上的由硬件到软件的层次分明的协议标准,不同网络厂商的设备及各种 Client/Server 之间就没有共同语言,互联互通更无从谈起,今天互联网的繁荣局面也只能在科幻的想象中存在。

层的抽象提高了程序结构设计的模块化,与此同时,也造就了软件工程师的角色与分工,不同角色的工程师工作在程序不同的层上:我们作为一家生产网络交换机嵌入式操作系统的软件公司,Linux 系统工程师和 ASIC/SDK 系统工程师工作在最底层,负责外围设备的驱动程序及交换芯片的统一的配置控制接口;中间层的协议栈工程师开发面向用户的网络功能;最上层的应用工程师提供特定应用场景的解决方案。混迹于职场许多年,就我所知,其他公司大概也有类似的根据层的工程师角色的划分。比如互联网公司中工程师常见的角色有前端工程师,后端工程师和算法工程师。

那么,测试工程师是否也工作在程序的某一层呢?恐怕很多测试工程师都不曾仔细思考过这个问题。测试不过就是测试,即使自动化测试也不生产包含在发布产品中的代码,更别提从来不用写代码的手工测试了。测试工程师莫非是跳出三界外,不在五行中,天不收,地不管的与程序无涉的特别的另类?!实则大谬不然,测试工程师包括做手工测试的测试工程师必须且只能工作在程序的某层上。否则,测试不过是开发的附庸和配角,焉能不委屈?!就敝公司而言,自动化测试的脚本验证的是产品的网络功能,调用的是 CLI (Command Line Interface) 的命令接口;而应用工程师做的很多项目也是基于系统的 CLI 实现的。因此,测试工程师和应用工程师工作的目标虽大相径庭,但工作的内容和方式却并没有多大区别,不过都是用 CLI 实现测试的用例或应用的功能。而做手工测试的测试工程师工作的对象也是 CLI,不同之处仅是以手工的方式完成自动化测试无法完成或不方便完成的测试用例。因此,我一直认为,从抽象的角度来看,无论是测试工程师和开发工程师都是程序员,重要的是,测试工程师应该用程序员的方法思考问题和解决问题,象程序员一样工作。我常常发现,测试工程师在向开发工程师报告 BUG 的同时,也忙于修改自己测试脚本的 BUG。谁说测试工程师不可以是程序员呢?!推而广之,上溯到公司的执行官乃至于政府的首脑和国家的元首,不都是工作在不同的层上的领导者吗?不相信吗?记得《天龙八部》中有这样一个情节:萧峰帮助辽帝耶律洪基平判后,赐爵楚王,官封南院大王,勾兑军国大事。“萧峰虽然从来没做过官,但他久为丐帮帮主,统率群豪,自有一番威严。带着丐帮豪杰和契丹大豪,其间也无甚差别。……一切均井井有条。”丐帮帮主是民间协会的领袖,南院大王是政府机构的官职,显然是不同的两个层,相差甚远。但是以萧峰的领导力,“自有一番威严”,无论是哪个岗位,乔帮主还是萧大王,“其间也无甚差别”。这可是扯的太远了。

没有比较就没有伤害,而有差异才会有比较。假如大家认同测试工程师和开发工程师都是程序员,那么,测试工程师和开发工程师之间就很自然地谈不到高低的差别。本来嘛,程序的层就是层,无论是上层还是下层,只是抽象级别的不同,并没有什么贵贱之分。说归说,而事实上,不仅有些开发工程师常常矫情地自别于测试工程师,不同角色的开发工程师之间相轻的情况也屡见不鲜。在互联网公司,广泛地流传着这样一种说法,算法工程师最有优越感,比之前端工程师,后端工程师更有优越感。反过来讲,前端工程师想做后端工程师,后端工程师想做算法工程师。人性不过如此,在虚荣心的召唤下,这种优越感往往建立在虚幻的感觉和想象之上,自认为所工作的层具有更高的技术门槛。信有之乎?系统工程师似乎比应用工程师更有优越感。可笑可悲可叹可厌可鄙的是,竟有工程师为了自己“优越”的地位,抱残守缺,象母鸡护小鸡一样紧紧地守卫着自己的那一亩三分地儿,绝不允许他人染指。宛如多疑的皇帝,一夕数惊,日夜不安的担心别人知道了自己的那点儿微末小技儿,被取而代之。既然有技术含量这样难以逾越的壁垒,还有什么可惊可怕的呢?可见,所谓的技术门槛这玩意儿并不是什么可侍的阿物儿。我常常戏称这样的井底之蛙为“小”程序员。

包括测试工程师在内的程序员的工作是彻底的脑力劳动。最高境界的修炼内化于基于数据和逻辑的即计算机科学特有的计算思维方法,包括抽象、分析、推理、递归等;最厉害的绝招外化于解决问题的能力和技巧,如简化、分而治之、转换成已知、由具体到一般、或由一般到具体、或由一般到具体且由具体到一般分进合击等等。程序员的修养钟情于不吝于动脑筋的测试工程师和开发工程师,在设计,架构,编码和测试的研发实践中不断地升华。这就是我眼中的程序员,不区分角色,测试工程师,开发工程师和架构师,就共同的方法和技巧而言,他们都是抽象意义上的“大”写的程序员。从一般的眼光看,架构师似乎更高明一点点儿,其实,人人都可以是架构师。编码不可能不涉及架构,同样地,测试不可能不涉及架构。你象架构师一样写代码和做测试,那就是架构师,我愿意用一个三段论(syllogism)的句式总结如下:程序员即是架构师;测试工程师和开发工程师都是程序员;所以测试工程师和开发工程师都是架构师。因此,江湖中传说有一种工程师叫做全栈工程师 (Full Stack Developer),十八般武艺,样样精通。从前端到后端,从系统到应用,从低层到高层,全都拿得起,放得下。写得了代码,做得了架构,干得了测试。上天入地,无所不能,宛如见首不见尾的神龙。问君何能尔?根固而木长,源深而流远,超然物外,心远地自偏,无招胜有招。而那些汲汲于一孔江山的“小”程序员的愚在于伐根以求木茂,塞源而欲流长,奚可焉?!

当然,测试工程师不一定非要精于编程之道,但必须熟稔程序员的的思维方法和解决问题的技巧,否则,只是只想只会根据开发工程师的指令亦步亦趋,甘心做个不动脑筋的操作员,如何担当得起测试工程师的职任?测试常常强调不留死角地覆盖功能的方方面面,更要求测试工程师能够从不同的角度思考问题,针对代码可能的罩门和软肋,检查合法输入,非法输入和边界输入的各种情况。鄙公司的用户,无论是数据中心网络还是企业网络,对我们研发的交换机网络操作系统的性能和稳定性的要求近乎苛刻,部署在生产网络的系统必须 7X24 小时无差错不宕机。应对这种挑战并非易事,我们必须系统地管控产品生产链的每个环节,包括需求、设计、架构、开发和测试。比如,测试用例必须系统地覆盖功能的每一个子功能点,请参考我的博客文章“用系统的方法做系统软件”。我们从事的是专业性很强的网络行业,测试工程师必须成为领域的知识专家,才有可能做到系统地测试。

朱兰博士认为,质量是产品或服务的适用性 (Fitness for Use)。因此,测试工程师常常需要验证用户典型的应用案例,以确保产品适用于特定的应用场景。在此过程中,测试工程师有机会学习面向用户的广阔的应用空间,这才是实打实的实锤实战经验,更需要时间的积累。不夸张地说,开发工程师正对着“小桥流水人家”浅唱低吟的时候,测试工程师已经昂首阔步向着星辰大海启航了。因此,和开发工程师相比较,测试工程师对程序底层的深度的理解虽然有所不及,但对产品应用的宽度的把握却远远过之。假设不论编码的基本能力,对于某个特定的工业领域,正是这种宽度和深度定义了工程师团队的技术实力和技术能力,也是量度和其他工程师团队距离的最好的度规 (Metric)。

诚然,研发过程中有效的组织 (Well Organized) 和良好的纪律 (Well Deciplined) 是质量的基本保证。毕竟,产品的质量取决于包含在程序中的每一行代码,而代码是开发工程师生产的,也是测试工程师验证的对象。因此,归根结底,有什么样的工程师,就有什么样的产品,好的产品质量是程序员当然包括测试工程师以自己的聪明才智创造 (干) 出来的。虚比浮夸,乞灵于管理流程的救赎,就好像马车跑的慢,不是打马而去打车,吾期期以为不可!

作者简介

贾彦民,目前就职于 PICA8。本科与研究生就读于重庆大学,2007 年获中国科学院软件研究所计算机软件与理论博士学位。爱读书,喜欢爬山与摄影(历史、文学、数学)。

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论