软件工程复习(六)

  • 通常把编码和测试统称为实现
  • 所谓编码就是把软件设计翻译成计算机可以理解的形式——用某种程序设计语言书写的程序
  • 编码是设计的自然结果,程序的质量主要取决于软件设计的质量
  • 但所选用的程序设计语言的特点和编码风格也会对程序的可靠性、可读性、可测试性和可维护性产生深远的影响。
  • 软件测试对软件可靠性的影响是重要的(软件生命周期中,差错不可避免
  • 尽可能早地发现并纠正差错
  • 测试的目的就是在软件投入生产性运行之前尽可能多地发现软件中的错误
  • 软件测试在软件生命周期中横跨两个阶段
    • 单元测试(一般模块的编写者和测试者是同一个人)
    • 综合测试(通常由专门的测试人员承担这项工作)
  • 统计资料表明,软件测试的工作量往往占软件开发总工作量的40%以上,对于关键应用系统的测试,可能相当于软件工程其他步骤总成本的3~5倍
  • 测试目标是发现软件中的错误,但不是最终目的。调试是诊断并改正错误,这是最终目的调试测试阶段最困难的工作
  • 对测试结果进行收集和评价,确定系统的可靠性

编码 (understanding)

  • 选择程序设计语言

    • 总的来说,高级语言明显优于汇编语言除了对程度执行时间和使用的空间都有很严格限制的情况;需要产生任意的甚至非法的指令序列;体系结构特殊的微处理机,以致在这类机器上通常不能实现高级语言编译程序;或者大型系统中执行时间非常关键的(或直接依赖于硬件的)一小部分代码;其他程序应该一律用高级语言书写
  • 编码风格

    源程序代码的逻辑简明清晰、易读易懂是好程序的一个重要标准,应该遵循下述规则

    • 程序内部的文档(注释)
    • 数据说明
    • 语句构造
    • 输入输出
    • 效率

软件测试基础 (master)

软件工程其他阶段都是“建设性的”,而测试阶段的目的却是为了“破坏”已经建造好的软件系统————竭力证明程序中有错误,不能按照预定要求正确工作。

  • 测试目标

    • 测试为发现程序中的错误执行程序的过程
    • 好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案
    • 成功的测试是发现了至今为止尚未发现的错误的测试
  • 测试准则

    • 所有的测试都应该能追溯到用户需求(因需制宜
    • 应该在测试开始之前的相当长时间,就制定出测试计划
    • 把Pareto原理应用于软件测试:测试发现的错误中的80%很可能是由程序中20%的模块造成的(2-8定理
    • 测试应该从“小规模”开始,并逐步进行“大规模”测试(由小到大
    • 穷举测试是不可能的
    • 为了达到最佳的测试效果,应该由独立的第三方来从事测试工作(心理学角度上来讲,让编码人员找自己的错误是不现实的
  • 测试方法

    • 黑盒测试

      • 黑盒测试又称为功能测试
      • 是在接口层面的测试,把程序看成一个黑盒子,不考虑内部结构和处理过程
      • 适合于程序后期测试接口
    • 白盒测试

      • 白盒测试又称为结构测试
      • 把程序看成装在一个透明的白盒子里,也就是完全了解程序的结构和处理过程
      • 按照程序内部的逻辑测试程序,检验程序中的每条通路是否都能按预定要求正确工作。
      • 适合于早期测试模块内部逻辑
  • 测试步骤

    • 单元测试,也称模块测试
    • 集成测试
    • 确认测试
    • 系统测试

      ps:

      • α测试:在受控环境下测试(实验室环境)
      • β测试:在非受控环境下测试(实际工作环境)

单元测试

  • 单元测试和编码属于软件工程过程同一阶段(同属于编码和单元测试,因为单元测试通常是在编码的过程中进行的)
  • 在编写出源程序代码并通过了编译程序的语法检查之后,应用人工测试计算机测试这样两种类型的测试,完成单元测试工作
  • 这两种类型的测试各有所长,互相补充
  • 单元测试主要采用白盒测试技术,而且对多个模块的测试可以并行地进行
  • 测试重点

    • 模块接口
    • 局部数据结构
    • 重要的执行通路
    • 出错处理通路
    • 边界条件
  • 代码审查

    人工测试源程序可以由编写者本人非正式地进行,也可以由审查小组正式进行后者称为代码审查,是一种非常有效的程序验证技术,对于典型的程序来说,可以查出30%~70%的逻辑设计错误和编码错误

    • 审查小组组成:
      • 组长,应该是一个很有能力的程序员,而且没有直接参与这项工程
      • 程序的设计者
      • 程序的编写者
      • 程序的测试者
  • 计算机测试

    • 模块并不是一个独立的程序,必须为每个单元测试开发驱动软件(负责调用本模块)和(或)存根软件(负责模拟被本模块调用的子模块,比如对于一个用于数据处理的子模块,本模块需要的是其输出一个数据结构,那么就可以直接用一个构造的数据实例来替代这个子模块)
    • 驱动程序就是一个“主程序”,接收测试数据,把这些数据传送给被测试的模块,并且输出有关结果
    • 存根程序代替被测试的模块所调用的模块,也可以称为“虚拟子程序”。(桩模块
    • 使用被它代替的模块的接口,做最少量的数据操作,输出对入口的检验或操作结果,并且把控制归还给调用它的模块

集成测试

集成测试是测试和组装软件的系统化技术,在把模块按照设计要求组装起来的同时进行测试,主要目标是发现与接口有关的问题

  • 非渐增式测试方法

    • 先分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序
    • 一次性集成测试
  • 渐增式测试

    把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试。这种每次增加一个模块的方法称为渐增式测试
  • 自顶向下集成

    • 首先测试M1(需要写M2、M3、S4对应的桩模块),然后将M1作为M2、M3、S4的驱动程序,集成并测试。以此类推
    • 不用写驱动模块,但是需要写大量桩模块
    • 优点:
      • 不需要测试驱动程序
      • 能够在测试阶段的早期实现并验证系统的主要功能
      • 能在早期发现上层模块的接口错误
    • 缺点:
      • 需要存根程序
      • 低层关键模块中的错误发现较晚
      • 早期不能充分展开人力(不利于团队分工测试)
  • 自底向上集成

    • 步骤:
      1. 把低层模块组合成实现某特定软件子功能的簇
      2. 写一个驱动程序(用于测试的控制程序),协调测试数据的输入和输出
      3. 对由模块组成的子功能簇进行测试
      4. 去掉驱动程序,沿软件结构自下向上移动,把子功能簇组合起来形成更大的子功能簇,重复2~4
    • 优缺点与自顶向下集成相反
  • 回归测试

    • 不是一种测试方法,也不是一个测试阶段。重新执行已经做过的测试的某个子集,以保证上述这些改变没有带来非预期的副作用
    • 回归测试(regression)与累进测试(progression)
      • 回归测试:用之前版本的测试方案,测试好现在的程序
      • 累进测试:用新的测试方案,测试同一版本的程序

确认测试

  • 确认测试也称为验收测试,目标是验证软件的有效性
  • 使用了确认(Validation)和验证(Verification)这样两个不同的术语
    • 验证 就是要用数据证明我们是不是在正确的制造产品。注意这里强调的是过程的正确性
    • 确认 就是要用数据证明我们是不是制造了正确的产品。注意这里强调的是结果的正确性
  • 确认测试的范围
  • 软件配置复查
  • Alpha和Beta测试
    • α测试:在受控环境下测试(实验室环境)
    • β测试:在非受控环境下测试(实际工作环境)

白盒测试技术 (master)

  • 逻辑覆盖是设计白盒测试方案的技术。设计测试方案是测试阶段的关键技术问题
  • 测试方案包括具体的测试目的(例如,要测试的具体功能),应该输入的测试数据预期的输出结果
  • 逻辑覆盖

    • 语句覆盖

      • 选择足够多的测试数据,使被测程序中每个语句至少执行一次
      • 很弱的逻辑覆盖标准
      • 只关心判定表达式的值,不关心表达式内部具体每个条件取哪些值
      • 举个栗子:
        • 只需选取一组测试数据,覆盖路径ace即可
        • 比如可以取
          • 测试用例的格式:【输入的(A, B, X),输出的(A, B, X)】
          • 满足语句覆盖的测试用例:【(2, 0, 4), (2, 0, 3)】
    • 判定覆盖

      • 不仅每个语句必须至少执行一次
      • 而且每个判定的每种可能的结果都应该至少执行一次,也就是每个判定的每个分支都至少执行一次
      • 同样只关心判定表达式的值,不关心表达式内部具体每个条件取哪些值
      • 举个栗子:
        • 测试用例的格式:【输入的(A, B, X),输出的(A, B, X)】
        • 满足判定覆盖的测试用例:
          • 【(2, 0, 4), (2, 0, 3)】覆盖ace
          • 【(1, 1, 1), (1, 1, 1)】覆盖abd
    • 条件覆盖

      • 不仅每个语句至少执行一次,而且判定表达式中每个条件都取到各种可能的结果
      • 条件覆盖不一定比判定覆盖强,甚至可能连语句覆盖都达不到
      • 举个栗子:
        • 本例中b处没有逻辑代码,如果有,则上面的第二种方案就达不到语句覆盖,但是能达到条件覆盖
    • 判定/条件覆盖

      • 判定覆盖不一定包含条件覆盖,条件覆盖也不一定包含判定覆盖,判定/条件覆盖能同时满足这两种覆盖标准
      • 举个栗子:
    • 条件组合覆盖

      • 条件组合覆盖就是设计足够的测试用例,运行被测程序,使得每个判断的所有可能的条件取值组合至少执行一次
      • 举个栗子:
        • 所有可能的条件组合:
        • 一个满足条件组合覆盖的测试方案
    • 点覆盖

    • 边覆盖

    • 路径覆盖

  • 控制结构测试

    • 基本路径测试 (master)

      1. 根据过程设计结果画出相应的流图
      2. 计算流图的环形复杂度n
      3. 确定线性独立路径的基本集合(找到n条能覆盖整个流图的独立路径
        • 独立路径指至少引入程序一个新处理语句集合或一个新条件的路径,用流图术语描述,独立路径至少包含一条在定义该路径之前不曾用过的边
        • 程序的环形复杂度决定了程序中独立路径的数量
        • 不考虑业务逻辑的情况下找到的路径有可能是无效的(多个判定条件之间可能有一定联系,导致某个独立路径上的判定条件无法同时满足)
      4. 设计可强制执行基本集合中每条路径测试用例
      • 举个栗子:
        • 环形复杂度:6
        • 路径:
          • 路径1:1-2-10-11-13
          • 路径2:1-2-10-12-13
          • 路径3:1-2-3-10-11-13
          • 路径4:1-2-3-4-5-8-9-2···
          • 路径5:1-2-3-4-5-6-8-9-2···
          • 路径6:1-2-3-4-5-6-7-8-9-2···
        • 路径4、5、6后面的省略号(…)表示,可以后接通过控制结构其余部分的任意路径(例如,10-11-13)
    • 条件测试 (pass)

    • 循环测试 (pass)

      • 简单循环
      • 串接循环
      • 嵌套循环

黑盒测试技术 (master)

  • 黑盒测试着重测试软件的功能需求,让软件工程师设计出能充分检查程序所有功能需求的输入条件集
  • 黑盒测试和白盒测试两者互补(A代表需求,B代表实现)
    • 白盒测试至多能覆盖实现
    • 黑盒测试至多能覆盖需求
    • 只有两者结合使用才能做到全方位的测试
  • 白盒测试在测试过程的早期阶段进行,而黑盒测试主要用于测试过程的后期
  • 黑盒测试力图发现下述类型的错误:① 功能不正确或遗漏了功能;② 界面错误;③ 数据结构错误或外部数据库访问错误;④ 性能错误;⑤ 初始化和终止错误
  • 等价划分

    • 目的

      • 需要研究程序的功能说明,确定输入数据的有效等价类和无效等价类
    • 经验

      • 如果规定了输入值的范围,则可划分出一个有效的等价类(输入值在此范围内),两个无效的等价类(输入值小于最小值或大于最大值)
      • 如果规定了输入数据的个数,则类似地也可以划分出一个有效的等价类和两个无效的等价类
      • 如果规定了输入数据的一组值,而且程序对不同输入值做不同处理,则每个允许的输入值是一个有效的等价类,此外还有一个无效的等价类(任一个不允许的输入值)
      • 如果规定了输入数据必须遵循的规则,则可以划分出一个有效的等价类(符合规则)和若干个无效的等价类(从各种不同角度违反规则)
      • 如果规定了输入数据为整型,则可以划分出正整数、零和负整数等三个有效类
      • 如果程序的处理对象是表格,则应该使用空表,以及含一项或多项的表
  • 边界值分析

    • 处理边界情况时程序最容易发生错误
    • 边界值处测试数据的选取
      • 刚好等于
      • 刚刚小于
      • 刚刚大于
    • 栗子:
  • 错误推测

    • 错误推测法在很大程度上靠直觉和经验进行
    • 基本想法是列举出程序中可能有的错误和容易发生错误的特殊情况,并且根据它们选择测试方案
  • 调试

    • 调试(也称为纠错)作为成功的测试的后果而出现,即调试是在测试发现错误之后排除错误的过程
    • 调试不是测试,但是它总是发生在测试之后
    • 途径:
      • 蛮干法:打印内存数据、关键输出、断点
      • 回溯法:由症状回推可能的原因
      • 原因排除法:对分查找、归纳、演绎等方法

软件可靠性

坚持原创技术分享,您的支持将鼓励我继续创作!