第七章“实现”主要包含编码工作和测试两大部分内容。
7.1 编码 (Coding)
定义:将软件设计结果翻译成某种程序设计语言,书写程序。
7.1.1 选择程序设计语言
- 语言分类:
- 汇编语言 (Assembly Language):将软件设计翻译成机器操作序列,表示方式不同,既困难又容易出错。
- 高级语言 (High-Level Language):如 Java, C#, C++, C, Python, MATLAB 等。
- 优势:
- 一句对应多句,效率更高(实现相同功能代码量更少)。
- 允许用户给程序变量或子程序赋予含义鲜明的名字,便于多人协作理解。
- 使用的符号和概念符合人的习惯思维 (如 if-else, =, +)。
- 书写、阅读、测试、调试和维护更容易。
- 应用现状:绝大多数情况下使用高级语言,极特别领域使用汇编语言。
- 优势:
- 语言选择标准 (7个):
- 系统用户的要求:用户可能指定特定语言以方便内部维护。
- 可使用的编译程序:目标环境提供的编译程序限制语言范围。
- 可得到的软件工具:方便的软件工具利于编写和验证。
- 工程的规模:有些语言更适合大型程序开发。
- 程序员的知识:程序员是否熟练掌握该语言。
- 可一致性要求:软件是否需分布到不同计算机上。
- 应用领域:不同领域对语言有使用限制。
7.1.2 编码风格 (Coding Style)
定义:编写程序时表现出的特点、习惯和逻辑思路。
良好风格的养成
(5个方面):
- 程序的内部文档:
- 恰当的标识符 (含义鲜明的名字):帮助阅读者理解程序。
- 适当的注解 (注释):程序员之间及对未来自己的重要通信手段,有助于程序理解。
- 程序的视觉组织 (代码排版):
- 对程序可读性有很大影响。
- 应利用适当的阶梯形式来表示层次结构 (如 if-then-else 的缩进)。
- 数据说明:
- 次序应标准化 (按类型或数据结构)。
- 多个变量名在同一语句说明时,应按字母顺序排列。
- 复杂数据结构应注释说明其实现方法和特点。
- 语句构造:
- 每个语句应清晰而简单,不应为提高效率使程序过分复杂。
- 效率:
- 指处理机时间和存储容量两方面。
- 效率是性能要求,应在需求分析阶段明确。
- 好的设计提高效率。
- 程序的效率和程序的简单程度应一致,不应牺牲清晰性和可读性不必要地提高效率。
- 程序的内部文档:
7.2 测试基础 (Testing Basics)
7.2.1 测试阶段的根本目标
- 尽可能多地发现并排除软件中潜藏的错误。
- 最终把一个高质量的软件交给用户使用。
- 强调:软件测试不可能排除所有的错误,因为技术和测试用例限制。
7.2.2 软件测试的目标 (定义)
- 测试是为了发现程序中错误而执行的过程。
- 好的测试方案是尽可能发现迄今为止尚未发现错误的测试方案。
- 成功的测试是发现了迄今为止尚未发现错误的测试。
7.2.3 测试准则 (7个)
- 所有测试都应追溯到用户需求:不符合需求是最严重的错误。
- 远在测试开始之前就应制定测试计划 (在设计阶段制定)。
- 应将二八原则 (Pareto principle) 应用到软件测试中。
- 应从小规模测试开始,逐步进行大规模测试 (如从单元测试到验收测试)。
- 穷举测试是不可能的:软件路径众多,测试用例无法穷尽,因此需要逻辑覆盖等方法。
- 为达到最佳测试效果,应有独立的第三方进行测试工作 (测试人员与代码编写人员分开)。
7.2.4 测试方法
- 黑盒测试 (Black-box Testing):
- 定义:把程序看作一个黑盒子,完全不考虑程序内部结构和处理过程,只在程序的接口进行测试。
- 检查内容:程序功能是否按规格说明书正常使用,能否接收数据并产生正确输出,运行中是否保持外部信息完整性。
- 别称:功能测试。
- 理解:只关心结果是否正确,不关心过程 (类比数学题,结果对过程错,黑盒老师判对)。
- 使用阶段:通常在后期进行。
- 发现错误类型:功能不正确或遗漏、界面错误、数据结构错误、性能错误、初始化和终止错误。
- 技术:等价划分、边界值分析、错误推测。
- 白盒测试 (White-box Testing):
- 定义:把程序装在透明盒子里,测试者完全知道程序的结构和处理算法,按照程序内部逻辑进行测试,检查执行通路是否按预定要求工作。
- 别称:结构测试。
- 理解:关心结果是否正确且过程是否正确 (类比数学题,过程错结果对,白盒老师判错)。
- 使用阶段:通常在早期使用。
- 关注重点:模块的接口、局部数据结构、重要的执行通路、出错处理通路、边界条件。
- 技术:
- 逻辑覆盖 (Logic Coverage):选取具有代表性的软件路径进行测试,是穷尽测试唯一可行的替代办法。
- 语句覆盖 (Statement Coverage):最弱,要求程序的每个语句都执行一次。
- 判定覆盖 (Decision Coverage / Branch Coverage):每个语句执行一次,且每一个判定结果的每种可能结果都执行一次 (每个分支至少执行一次)。
- 条件覆盖 (Condition Coverage):每个语句执行一次,且判定表达式的每个条件都能取得各种可能结果。
- 判定/条件组合覆盖等 (要求更严格)。
- 逻辑覆盖 (Logic Coverage):选取具有代表性的软件路径进行测试,是穷尽测试唯一可行的替代办法。
- 控制结构测试 (Control Structure Testing):
- 基本路径测试 (Basis Path Testing):非常重要。
- 步骤:
- 根据设计结果画出相应的流图。
- 计算环形复杂度 (Cyclomatic Complexity):
- 方法一:
V(G) = P + 1
(P为判定节点数)。 - 方法二:数闭区间数量加一。
- 方法一:
- 确定线性独立路径的基本集合:独立路径指至少引入程序一条新处理语句或一条新条件路径,或一条之前未使用的边。路径数量等于环形复杂度。
- 设计测试用例强制执行基本集合中的每条路径。
- 步骤:
- 条件测试、循环测试。
- 基本路径测试 (Basis Path Testing):非常重要。
- 灰盒测试 (Grey-box Testing):介于黑盒与白盒之间的一种测试方法。
7.2.5 测试步骤 (按顺序,及发现错误类型)
- 模块测试 (Module Testing) / 单元测试 (Unit Testing):
- 目标:保证每个模块作为单元正常运行。
- 发现错误类型:编码和详细设计的错误。
- 主要使用:白盒测试技术。
- 手段:
- 代码审查 (Code Review):人工进行,可由编写者非正式进行,或由审查小组正式进行 (组长、设计者、编写者、测试者组成)。高效,能查出30%-70%的逻辑设计错误和编码错误。与计算机测试互补。
- 计算机测试 (Computer Testing):
- 驱动软件 (Driver Software):假的主程序,接收测试数据,传输给被测试模块,并打印结果。用于测试底层模块。
- 存根软件 (Stub Software):被代替模块所调用的模块 (虚拟子程序),用于代替底层小弟接收指令。用于测试顶层模块。
- 缺陷:驱动和存根软件代表开销,增加成本。
- 子系统测试 (Subsystem Testing):
- 目标:将单元模块放在一起,测试模块相互之间的协调、通信。
- 发现错误类型:模块接口错误。
- 特点:兼有测试和组装两重含义,常称之为集成测试的一部分。
- 系统测试 (System Testing):
- 目标:将测试的子系统装配成一个完整的整体进行测试。
- 发现错误类型:软件设计中的错误以及需求说明中的错误。
- 特点:兼有测试和组装两重含义,常称之为集成测试的一部分。
- 集成测试 (Integration Testing):
- 包含:子系统测试和系统测试。
- 模块组装方法:
- 非渐增式 (Non-incremental):先分别测试每个模块,再全部组装成程序。
- 渐增式 (Incremental):将下一个要测试的模块与已测试好的模块结合,每次增加一个。更彻底,易于定位错误位置,通常推荐使用。
- 渐增式策略 (两种):
- 自顶向下集成 (Top-down Integration):
- 从主控模块 (上层) 开始向下移动,逐渐结合模块。
- 优点:能在测试早期验证软件主要功能,早期发现上层模块的接口错误。
- 缺点:需要存根程序,底层发现错误较晚。
- 自底向上集成 (Bottom-up Integration):
- 从原子模块 (底层) 开始向上组装测试。
- 优点:不需要存根程序,底层模块发现错误较早,能充分展示人力。
- 缺点:需要驱动程序,验证主要功能和发现上层接口错误较晚。
- 自顶向下集成 (Top-down Integration):
- 回归测试 (Regression Testing):
- 目的:执行已做过测试的某个子集,保证软件由于调试或其他原因引起的变动不会带来非预期副作用 (如引入新的错误)。
- 验收测试 (Acceptance Testing) / 确认测试 (Confirmation Testing):
- 目标:把软件作为一个单一实体进行测试,验证软件的有效性 (软件功能和性能如同用户合理期待)。
- 特点:用户积极参与,主要使用实际数据。
- 发现错误类型:需求规格说明书中的错误。
- 主要使用:黑盒测试。
- 重要内容:软件配置复查,确保所有成分齐全,质量符合要求,文档和程序一致,便于维护。
- 分类:
- 阿尔法测试 (Alpha Testing):
- 在开发者场所进行,开发者在用户指导下进行,记录错误,受控环境。开发者是主导地位。
- 贝塔测试 (Beta Testing):
- 由最终用户在一个或多个客户场所进行,开发者通常不在场,软件在开发者不能控制的环境下进行真实应用。用户拥有更多权利。
- 阿尔法测试 (Alpha Testing):
- 平行运行 (Parallel Running):
- 定义:同时运行新开发的系统和即将被取代的旧系统。
- 目的/优点:
- 可在准生产环境中运行新系统,不冒风险。
- 用户有时间熟悉新系统。
- 可以验证用户手册等文档。
- 以准生产模式验证性能指标 (如负载冲击测试)。
7.2.6 测试阶段的信息流 (使用的文档资料)
- 软件配置文档:
- 需求说明书:用于编写用例,检查是否满足需求。
- 设计说明书:用于检测软件设计问题和进行白盒测试。
- 源程序清单 (代码)。
- 测试相关文档:
- 测试计划。
- 测试方案:不仅包含输入数据,还需有每组数据预期检验的功能和预期得到的正确输出 (像有题有答案)。
7.8 调试 (Debugging)
- 定义:作为成功测试的后果出现,是发现错误后排错的过程。
- 过程:执行测试用例 -> 产生结果 -> 调试 (一眼识出/推测验证) -> 确认改正。
- 调试困难的原因 (心理、技术、软件固有特征):
- 症状与问题发生地距离远。
- 改正错误后症状暂时消失。
- 症状并非由错误引起。
- 症状可能由不易跟踪的人为引起。
- 症状可能由定时问题引起。
- 很难产生完全一样的输入条件 (难以复现)。
- 症状时有时无。
- 症状可能分布在多个任务中。
- 调试的途径 (3种):
- 蛮干法 (Brute-force Method):其他方法失效后的最后手段,地毯式搜索。
- 回溯法 (Backtracking Method):常用调试方法,从发现症状的地方人工沿控制流程回溯追踪错误点,小程序适用。
- 原因排除法 (Cause Elimination Method):
- 对分查找法 (Binary Partitioning):将程序一分为二,逐步缩小错误范围。
- 归纳法 (Induction):从个别现象推断一般结论,组织分析错误数据。
- 演绎法 (Deduction):从原理和前提出发,设想问题原因并逐个验证。
7.9 软件可靠性 (Software Reliability)
- 定义:
- 软件可靠性:在给定时间间隔内成功运行的概率。
- 软件可用性 (Availability):在给定时间点成功运行的概率。
- 估算平均无故障时间的方法:MTTF (Mean Time To Failure) 是一个重要的参考指标。