开发者的干爹
读《现代软件工程——构建之法》第二章有感
上回说到,较之所谓“多门技术一把抓”的大牛,“编程思维”更加重要。如果编程者是艺术家,那么“思维”就好比是艺术家的灵魂。
然而仅仅凭借“思维”,开发者就能将“开发”的瞬间完成吗?孟子云“挟泰山以超北海,诚不能也”,就好比是,牛皮不是吹的,泰山不是堆的,火车更不是用手推的。哲学中讲,物质依赖于过程而运动,因运动而存在。而在软件开发中,这种过程就是开发流程。
一、被忽视的牛人
“看似很普通,其实很厉害”是许多“牛人”的通性,单元测试就是这么个“狠角色”。根据不同时期的任务划分,软件的生命周期可分为:软件定义,软件设计,软件开发和单元测试,集成测试,部署以及调试等6个阶段【1】。
从理论上讲,测试的地位很特殊。其特殊表现在,其功能是破坏性的。其目的旨在证明程序的“不健全性”。
测试包括:单元测试和集成测试,而单元测试却常常被归入到“软件的实现”当中【1】。单元测试与集成测试具有相同和不同之处,相同之处表现为,二者都是为了“找茬”,而单元测试更偏向于将功能能够进一步推进。
在需要“合作”的实际项目中,很多错误来源于自身对模块功能的误解、疏忽以及对于模块变化的不了解。正因为“单元测试”的存在,模块功能定义更明确、对其他模块的影响更少,从而保证了模块的质量。
二、敢问英雄大名
1.定义:“单元测试”是程序针对某一功能区所进行的验证其自身不合理性的过程【2】。
2.目的:找出功能部分的不合理性【2】。
3.步骤【2】:
A.设置数据——对功能部分进行测试。
B.对目标功能进行测试
C.比较实际、预期结果的差别
三、眼里不揉沙子
1.测试不是为了证明程序是对的,而是尽量证明程序是错的【1】。
2.其目的【1】:
A.测试是为了发现程序中的错误而执行程序的过程
B.好的测试方案极可能是发现迄今为止尚未发现的错误的测试方案
C.成功的测试是发现至今为止尚未发现的错误测试。
四、我是有组织的
知识从来都不会单独存在,往往都是通过一个个的节点,将看似分散的知识片段进行整合。
测试也是一样,它具有自己的组织体系,根据张海藩、吕云翔的《软件工程(第四版)》中所述,根据测试方式的不同,测试分为如下2个部分:
1.黑盒测试
不考虑程序内部的数据结构和处理过程,其只在接口进行测试,检查程序功能是否按照规格说明书的正常使用,是否能正确接受输入数据,产生输出信息,并且保持外部信息的完整性(包括:等价类划分和边界值分析)【1】。
2.白盒测试
完全了解程序的结构和处理过程。此方法按照程序内部的逻辑测试程序。
白盒测试还包括:逻辑覆盖(语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖以及条件组合覆盖)以及控制结构测试(基本路径测试、条件测试以及数据流测试)【1】。
五、作为大哥的标准
第一点曾经说过,单元测试更偏向于推动开发过程的向前发展。这就像是一个带头大哥,然而“大哥不是你想当,相当就能当”,这需要一个标准【2】,如下:
1.单元测试应该在最基本的功能/参数上验证程序的正确性。
A.从面向对象的设计原理出发,最基本的功能点由一个类及方法表现。
B.单元测试要测试API的每一个方法和参数。
2.单元测试需要由最熟悉代码的开发者书写 谁写的代码谁清楚,心里还没点数吗?
3.单元测试后,机器状态不变
“城头变幻大王旗”,为了使得测试充分,单元测试当然不会只执行一次,而是会有很多次,需要将各次的测试实例之间不互相干扰。
4.单元测试要快 只有当测试速度变快,才能从直观上,看出程序效率。
5.单元测试应该产生可重复、一致的结果。然而不要希望测试能够发现所有问题。
6.独立性——状态不依赖于别的测试
7.单元测试应该覆盖所有代码路径,此处联想到白盒测试中的基本路径测试的相关内容。为保证代码覆盖率,需以公开和私有的函数/方法开刀。其中,100%的代码覆盖率不等于100%的正确性。
A.代码覆盖率对于“应写而未写”的代码无能为力。
B.代码中的效能问题
C.多线程环境中的同步问题,这个问题和代码的时序、共享资源锁定有关。
D.“覆盖率”的层次:函数、语句、分支、条件
8.单元测试应该集成到自动测试的框架中
9.单元测试必须和产品代码一起保存和维护
六、回归测试很有用(回归到以前不正常的状态)
程序永远开发和测试不会终结,都是一个里程碑性质不断迭代和推进,当下程序对吗?好用吗?整合了新模块以后呢?为了使程序在不断补充功能以后都强劲,需进行回归测试。其主要目的有【2】:
1.验证代码的确改正了缺陷
2.同时证明新代码未破坏现有功能
除此之外,“回归”二字应理解为“回归以前不正常状态”,最好自动化,如此可以对每个构建快速运行的回归测试,然而如何将测试进行现实性的操作呢?
分析的主要方法有2种:1.抽样2.代码注入【2】,其中包含顺序为:先用抽样方法找到目标瓶颈所在,后用代码注入方式将检测的代码注入到每一个函数中。
实例: 调用频繁会造成程序的开销很大。
for(int i=0;i<list.size();i++)此句话会占用非常多的资源。
但是如果将list.size()移除循环,则会将资源占用大幅降低;由此说明,若我们不经分析就盲目优化,未必会效果好。
七、你自己的活儿(个人开发流程)
无论是团队还是机体都会面临相似的问题,如何开发?如何有效?如果闭目造车,想当然,我想漏洞很多,效率很差,这些都是不可取的。
未解决这个问题,这里引入了PSP阶段概念,即个人开发流程,这作为个人进行开发的主要指导,从而将理论与实际相结合,
PSP阶段【2】
1.计划 明确需求和其他相关因素,估计每个阶段的时间成本
2.开发
a) 需求分析 b) 生成设计文档 c) 设计复审 d) 代码规范 e) 具体设计 f) 具体编码 g) 代码复审 h) 测试(自测、修改代码、提交修改)
3.报告
a)计算工作量 b)事后总结 c)提出过程改进计划
其独具匠心的特点【2】:
1.不局限于某一技术
2.不依赖于考试
3.在小型、初创团队,质量不高不能全部归咎于程序员。
4.依赖于数据
5.目的是记录工程师如何实现需求的效率而非满意度。
八、实践中的“发光发热”
测试的基本准则是“可以扩展,不可修改”,对于扩展而言,我们主要分别从1.数据 2.需求 3.用户 4.软件构建 等四个层次进行扩展【2】。然而如果需要“有的放矢”的话,我们会将精力投放于对于“功能需求”的细分层次上,可以将功能需求从3个层次进行划分,即:1.基本功能 2.扩展功能 3.高级功能。
与单元测试偏向“推动开发”的特点不同,回归测试是为了找错从而能够将测试的自动化程度不断提高【2】,基本要求为:
1.手动测试,手工比较
2.不断测试
3.把测试文件、正确测试结果保存文件中,测试驱动只要比较测试的输出和标准结果就能得到答案。
4.再进一步,把自动构建脚本和构建验证测试结合起来。每次构件后,自动测试记录Bug。
综上所述,对第二章内容主要是“单元测试”的必要性和优越性,做一个比较全面的概述和感悟。
参考文献
【1】张海藩 吕云翔 《软件工程(第四版)》 2013.08.01 人民邮电出版社
【2】邹欣 《现代软件工程-构建之法(第三版)》 2017.03 人民邮电出版社