“大家好,我是程旭圆,最近过得不怎么好!需求变来变去,让我非常烦躁。bug 改不完,旧的去了新的来。更过分的是,上星期那个 bug 竟然又出现了,苦苦找不到原因,太诡异了。不知道做到什么时候是个头,好不容易做完的功能,结果被各种喷。产品真是**!”
这是不是你现在或者曾经有过的状态?负责任地讲,**的不是产品自己,是项目相关的所有人,包括用户自己。当雪崩发生时,没有一片雪花是无辜的。有没有科学的方法来避免这种困惑?作为一个程序员,能做些什么?答案是肯定的:请你站在巨人的肩膀上,用科学的方法提高交付质量!
01 本文概要
- 正确认识软件质量
- 软件开发生命周期与质量
- 业界关于质量保证的措施
- TDD 在敏捷开发中的角色
- 从 UnitTest 开始,向 TDD 靠拢
- 抛砖引玉,一个简单的案例
关注公众号:码神手记,第一时间获取最新干货
02 正确认识软件质量
关于软件质量,每个人都可以想到若干个词汇来形容它,但不一定全面。国家标准《GB/T 32904-2016 软件质量量化评价规范》中定义了软件质量模型,评价一个软件的质量时,可以用该模型作为参考。
软件质量模型一共分为 6 个方面:
- 功能性:完备性、正确性、恰当性、互操作性、保密安全性、依从性。
- 可靠性:成熟性、容错性、易恢复性。
- 易用性:易理解性、易学性、易操作性。
- 效率:时间特性、容量、资源利用性。
- 维护性:易分析性、模块化、规范性、易改变性、稳定性、可验证性。
- 移植性:适应性、易替换性、易安装性、移植完整性。
03 软件开发生命周期与质量
软件的开发需要经历一系列的步骤,这就是软件开发的生命周期。在国家标准《GB8566-88 计算机软件开发规范-软件开发生命周期》中,将软件开发按时间序列划分为以下 8 个步骤:
- 可行性研究与计划:调研技术、经济、政策、法规、集团/公司战略等各方面的可行性,也包括对用户需求的调研以及现有系统的分析。最终输出可行性研究报告,进行立项,确定项目范围。
- 需求分析:确认系统应该具备的具体功能,梳理功能流程图、数据流图、数据字典等,输出需求规格说明书。
- 概要设计:确定系统模块划分、模块间依赖关系以及接口规范、全局数据结构等,输出概要设计说明书。
- 详细设计:确定各模块实现细节,算法描述,输出详细设计说明书。
- 实现:根据详细设计进行编码开发。
- 集成/组装测试:确认系统功能是否满足需求规格说明书的要求。
- 确认测试:由用户参与,在系统上进行实际操作,验证系统是否满足使用需求。
- 使用和维护:准备运行环境,正式发布上线,投入使用。根据用户反馈进行系统的维护、更新工作。
软件开发生生命周期的每一个步骤都直接或者间接地决定着软件的质量,每一个步骤的错误都会传递下去,并对下一个步骤产生负面影响。因此,软件质量的问题并不只是程序员的问题,也不只是产品经理/项目经理的问题,整个生命周期中的任何一个角色都对质量负有责任。具体怎么负责呢?请继续往下看。
04 业界关于质量保证的措施
业界关于质量保证的方法措施无外乎两种:评审、测试。评审不讲究方法会导致无效会议,时间浪费,给后续的兄弟们挖下一个个大坑。测试不讲究方法,软件质量就失去了最后一道防线。
评审
评审包含以下 8 个方面:
- 需求评审:可行性、适用。
- 概要设计评审:总体结构、组件功能分配、组件间接口协议、全局数据结构。
- 详细设计评审:每个组件的功能、算法、过程描述。
- 软件验证和确认评审:用户参与,故事推演。
- 功能检查:功能已满足需求说明的所有需求。
- 物理检查:程序与运维文档一致,设备、网络条件已具备,做好交付准备。
- 综合检查:代码和设计一致、接口和规格说明一致、实现和需求一致、功能和测试描述一致。
- 管理评审:定期评审计划执行情况。
测试
测试可概括为测试阶段和测试方法两个方面。
测试阶段
- 单元测试:发生在编程阶段,检查子模块接口,局部数据结构,重要执行通路,出错路径,边界条件。
- 集成测试:发生在模块组装阶段,检查模块之间的通信。
- 系统测试:发生在交付之前,检查功能性能是否符合使用要求,配置文档检查,客户确认与验收。
测试方法
- 白盒测试:发生在单元测试阶段,关注的是逻辑覆盖,确保尽可能多的语句、分支、条件、分支/条件组合、路径都能被单元测试覆盖到,提高覆盖率。
- 黑盒测试:发生在集成、系统测试阶段,关注的是输入与输出是否符合预期,不关心内部细节、分支结构。
无论是在哪个阶段,采用什么样的方法,即便是只做黑盒测试,都需要面临一个问题:测试用例怎么写?有如下方法:
- 等价类划分:有测试数据集合 A,A 中有 A1、A2。A1 能产生的效果,A2 也能,A1 不能产生的效果,A2 也不能。那么 A1 和 A2 就属于等价类,A 是一个等价类集合。
- 边值分析:作为等价类划分的补充。考虑输入空值、最大值、最小值、越界的值等边界情况。
- 错误推测:根据经验,在某些情况下通常会发生一些错误。那么就把相应的数据作为测试数据。
- 因果图:考虑不同参数之间存在的约束、组合关系。
建议在梳理测试用例时使用正交分解思想,并充分利用思维导图工具提高效率。
05 TDD 在敏捷开发中的角色
我为什么会提到 TDD(Testing Driven Development)?TDD 与开发方法有关,每一个产品/项目的研发都会采用一定的方法。在软件开发领域,敏捷开发大行其道,一切为了快速交付。敏捷开发有许多优秀的实践,我列举以下 4 个:
- 极限编程(XP):适用于不确定性的项目,也是最广为流传的敏捷开发方法。简单、沟通、反馈、勇气是其四大价值观,在快速迭代中完善整体计划。四大价值观下隐藏着非常重要的一点,那就是团队成员之间的尊重。
- 特征驱动:适用于信息系统建设这一类项目,由业务痛点指导研发方向。人是决定性因素,领域专家、项目经理、技术经理、架构师、主程序员、程序员,每个人要扮演好自己的角色,输出自己的价值。项目应具有精确的进度报告和状态信息,不断交付可运行软件。
- Scrum:适用于复杂、创新性项目。采用较短的迭代周期,按价值确定若干待办事项的优先级,始终优先处理高价值事项。
- 水晶方法:适用于常规维护性项目。需要良好的持续集成环境支持,频繁集成、频繁测试、暴露问题、获取反馈并反思改进。
软件开发行业的不确定性不可避免,在互联网公司尤为明显,在实践当中也可能会将以上开发方式混合使用。TDD 是敏捷开发的核心实践和技术,它在以上任何一种开发方式中都可以使用,TDD 的目标是够用且干净的代码。如果你是个普通程序员,无法决策团队的质量保证流程。那么,你可以提出你的建议并做好自己,尝试 TDD。
06 从 UnitTest 向 TDD 靠拢
单元测试并不是 TDD 的全部,但它是一种向 TDD 靠拢的绝佳方式。帮助我们更加关注需求、问正确的问题、做正确而不多余的事情、提高设计能力、得到够用且干净的代码、不必担心自己的某个改动牵一发而动全身。长远来看,你得到的收益将会大于写测试用例的成本投入。
TDD 的核心步骤:
- 写一个测试用例。
- 实现,让测试用例运行通过
- 重构,消除代码中的坏味道。
我列出了一些点,可以作为落地 TDD 的参考:
- 从眼前开始,从最重要的核心特性开始。
- 要新增功能?先写个 TestCase 吧!
- 解释如何使用我的组件?用 TestCase 吧!
- 想实验某个三方组件的功能?用 TestCase 吧!
- 不要让 TestCase 之间互相依赖影响,保持独立。
- 给 bug 写个测试用例,避免以后重复出现。
- 保持只有一个 Case 不能运行通过,一个一个来。
- 不要追求形式、切勿教条主义,能一次完成设计优雅的代码总比多次重构节省时间。
- 关注测试覆盖率,逐渐提高覆盖率或简化实现(倒逼自己做出更好的设计)。
- 用断言提出正确的问题(提出正确的问题比解决问题更加重要,就好比选对方向比努力更重要)。
07 抛砖引玉:一个简单案例
开发语言:Java
应用框架:SpingBoot 2.4.2
测试框架:junit-jupiter 5.7.0 spring-boot-starter-test
示例功能:用户登录(演示 demo)
GitHub 地址:
https://github.com/iSuperCoder/tdd-springboot
08 总结
我们首先建立了统一的理论认知,然后又介绍了一些久经考验的方法,最后提出了一些落地的建议和示例,不妨把这篇文章作为自己的指导大纲吧。
通过本文的学习,应当有以下收获:
- 以国家标准为背书,根据质量模型全面认知了软件质量的评价标准。
- 软件开发生命周期中的任何一个步骤都会影响质量,负面的影响也将会传递到下一个环节。
- 业界关于软件质量保证的措施分为两种:评审、测试。评审可有效地将错误扼杀在摇篮中,测试则是软件质量的最后一道门。
- 不确定性无法避免,敏捷开发的不同实践适用于不同的项目场景。
- TDD 在任何一种开发模式中都可以使用,它是敏捷开发思想的核心实践与技术,目标是够用且干净的代码。
- TDD 落地一定会有不适感,需避免形而上学、教条主义。从眼前开始,从单元测试开始,拥抱 TDD。
最后把我曾看到过的一段话送给大家:
任何新的技术,尤其是会改变人行为习惯的技术,总是信的人越来越信,不信的人总能找到反驳的理由。
消极的人即便是把卡奈尔的书倒背如流也无法获得积极的心态,但是当你用积极的心态获得成功时,你就再也离不开它了。