软件构造-第六章-第五节-软件测试与测试优先编程


软件构造第六章 第五节 软件测试与测试优先编程

一、测试和测试优先编程

1.1 什么是测试

  测试提高软件质量的重要手段(但不是最重要的手段),执行程序或应用程序的过程,目的是发现错误(或其他缺陷),并验证软件产品是否适合使用。确认是否达到可用级别(用户需求),它关注系统的某一侧面的质量特性,一般来说,这些属性表示正在测试的组件或系统的程度。

1.2 测试的目标

  测试的目标与其他开发活动的目标背道而驰,目标是找出错误,再好的测试也无法证明系统里不存在错误

好的测试特点:

  1. 能发现错误
  2. 不冗余
  3. 具有最佳特性
  4. 既不复杂也不简单

1.3 测试的分类

  1. 单元测试:针对软件的最小单元模型开展测试,隔离各个模块,容易定位错误和调试。
  2. 集成测试
  3. 系统测试
  4. 回归测试
  5. 验收测试

1.4 静态测试与动态测试

  1. 静态测试
     静态测试通常是隐式的,例如校对以及编程时工具/文本编辑器检查源代码结构或编译器(预编译器)检查语法和数据流,作为静态程序分析。
     包括代码复查,总览和静态检查。


  2. 动态测试
    动态测试描述了对动态行为的测试代码,实际上执行给定集合的编程代码测试用例。

    • 动态测试可能会在程序按顺序完成100%之前开始测试代码的特定部分并应用于离散函数或模块。
    • 常使用存根、驱动程序或使用调试器。
  3. 测试与调试区别
    测试的目的是发现是否存在错误。
    调试的目的是识别错误,消除错误。

二、黑盒测试和白盒测试

2.1 黑盒测试

  1. 定义
     对程序外部表现出来的行为的测试,用于检查代码的功能,不关心内部的实现。


  2. 黑盒测试用例构造标准
     黑盒测试的测试用例围绕规约和要求,即应用程序应该做什么。检查程序是否符合规约。
     要求用尽可能少的测试用例,尽快运行,并尽可能大的发现程序的错误。


  3. 构造测试用例的方法

  • 等价类划分
     将被测函数的输入域划分为等价类,从等价类中导出测试用例
     针对每个输入数据需要满足的约束条件,划分等价类(自反、传递、对称),等价类划分可有两种不同的情况:有效等价类和无效等价类。
     等价类划分原理:基于的假设:相似的输入,将会展示相似的行为。故可从每个等价类中选一个代表作为测试用例即可。


    • 边界值分析
       大量的错误发生在边界而不是中央。这是对等价划分方法的一个补充,将边界作为等价类之一进行考虑,通常边界值的左右也是需要进行考虑的。
       边界值分析法是对输入输出的边界值进行测试一种黑盒测试方法,是对等价类分析法的补充。
       方法:找到有效数据和无效数据的分界点(最大值、最小值),对该分界点以及两边的值分别单独进行测试。
  • 两种比较极端的情况:

    • 笛卡尔积,全覆盖策略
      多个划分维度上的多个取值,要组合起来,每个组合要有一个测试用例(测试代价高),但并非所有组合的情况都有可能。
    • Cover each part 策略
      每个维度上的每个取值,只要被覆盖过一次即可(测试代价低),测试覆盖度未必高。
  • 等价类划分法与边界值分析法区别:
    等价类划分法可以挑选等价类范围内任意一个数据作为代表;
    而边界值分析法要求每个边界值都要作为测试条件。


2.2 白盒测试

  1. 定义
     在了解函数内部实际功能情况下,根据函数具体实施来选择测试用例。即要考虑内部函数实现细节。


  2. 白盒测试
     从系统的内部视角以及编程技能的角度来设计测试用例。
     测试人员选择合适的输入以遍历代码的路径并确定适当的输出。
     白盒测试可以应用于软件测试过程的单元,集成和系统级别。 通常在测试过程的早期进行的。


  3. 白盒测试测试用例构造

  • 一般测试用例构造方法
     确保模块中的所有独立路径均被至少执行一次。
     测试所有条件判断的 true condition 路径和 false condition 路径。
     测试边界范围内和操作范围内所有循环。
     测试内部数据结构以确保其有效性。


  • 独立/基本路径测试:
     对程序所有执行路径进行等价类划分,找出有代表性的最简单的路径(例如循环只需执行1次),设计测试用例使每一条基本路径被至少覆盖1次。


2.3 为什么说测试困难

  1. 软件方面原因:
  • 软件行为在离散输入空间中差异巨大
  • 大多数正确 少数错误
  • bug出现不遵循特定概率分布
  • 软件具体表现无统计规律可循
  1. 测试方面原因:
  • 对于所有情况暴力穷举不可能
  • 偶然测试没有意义
  • 基于样本的统计数据对软件测试意义不大,软件与其他产品的巨大差异

三、测试优先编程

3.1 测试优先编程

  1. 定义:
    在具体编写代码之前编写测试用例。
  2. 含义:
    以“失败”为目标,致力于找出代码的错误。
  3. 过程:
    • 先写spec
    • 再写符合spec的测试用例
    • 写代码、执行测试、有问题再改、再执行测试用例,直到通过

3.2 规约(specification)

  1. 定义

    • 规约描述了该方法的输入和输出行为。
    • 规约给出了参数的类型以及对它们(例如sqrt()的参数必须为非负数)的限制。
    • 它还给出了返回值的类型以及与返回值相关系的输入。
    • 在规约中,规范包括方法签名并且在上面的注释中描述其功能。
  2. 规约与测试联系

    • 编写测试是理解规约的好方法。
    • 规约也可能有错误,不完整,模棱两可,缺少边界情况约束的情况。
    • 编写测试用例可以在错误出现之前尽早发现,以免这些问题出现后浪费时间编写bug的测试用例。
  3. 何为规约?(第三章详细叙述)

    图3-1 规约示例

四、代码覆盖度

定义:已有的测试用例有多大程度覆盖了被测程序;
代码覆盖度越低,测试越不充分;但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高;
代码覆盖率高的程序在测试期间执行了更多的源代码,与低代码覆盖率的程序相比,包含未检测到的软件错误的可能性较低
基本覆盖标准:函数覆盖;语句覆盖、分支覆盖、
条件覆盖、 路径覆盖
测试效果:路径 > 分支 > 语句
测试难度:路径 > 分支 > 语句

五、以注释的形式撰写测试策略

“测试策略”通俗来讲就是6个字:“测什么”和“怎么测”。测试策略非常重要,需要在程序中显性记录下来。
目的:在代码评审过程中,其他人能够理解你的测试,并评判测试是否充分
在测试类的顶端写策略   

在每个测试方法前说明测试用例是如何选择的   

六、JUnit 测试用例写法

6.1 JUnit测试

Junit单元测试是依据 注释中@Test 之前的方法编写的
JUnit测试经常调用多次方法,常使用 assertEqual || assertTrue || assertFalse 来检查结果
@Before:准备测试、完成初始化,每个测试方法前执行一次
@After:清理现场,每个测试方法后执行一次
@Test:表明测试方法,内含Assert语句
第一个参数是预期结果、第二个参数实施及结果;
如果断言失败,该测试方法直接返回,Junit记录该测试的失败;
一个测试方法失败,其他测试方法仍运行
@Test(expected = *.class):对错误的测试,expected的属性值是一个异常
@Test(timeout = xxx):测试方法在制定的时间之内没有运行完则失败
@ignore:忽略测试方法

代码示例:

 public class Calculator {
     public int evaluate(String expression) {
         int sum = 0;
         for (String summand: expression.split("\\+"))
             sum += Integer.valueOf(summand);
         return sum;
     }
 }
  ---------------------------------------
 import static org.junit.Assert.assertEquals;
 import org.junit.Test;

 public class CalculatorTest {
 @Test
 public void evaluatesExpression() {
         Calculator calculator = new Calculator();
         int sum = calculator.evaluate("1 + 2 + 3");
         assertEquals(6, sum);
     }
 } 

6.2 JUnit测试常用方法

 assertArrayEquals("failure - byte arrays not same", expected, actual);
 assertEquals("failure - strings are not equal", "text", "text");
 assertFalse("failure - should be false", false);
 assertNotNull("should not be null", new Object());
 assertNotSame("should not be same Object", new Object(), new Object());
 assertNull("should be null", null);
 assertSame("should be same", aNumber, aNumber);
 assertTrue("failure - should be true", true);
 assertThat("albumen", both(containsString("a")).and(containsString("b")));
 assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
 assertThat("good", allOf(equalTo("good"), startsWith("good")));

更多方法见JUnit测试方法


文章作者: Demerzel Sun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Demerzel Sun !
评论
 上一篇
软件构造-第二章-第一节-软件生命周期和版本控制 软件构造-第二章-第一节-软件生命周期和版本控制
软件构造-第二章-第一节-软件生命周期和版本控制 一、软件开发基本过程1.1 软件生命周期 软件开发生命周期——从0到1 策划阶段:获取需求、制定计划 架构师:系统分析(业务领域,what)、软件设计(语言、架构,how) 编码实现
2020-03-17
下一篇 
软件构造第一章总结 软件构造第一章总结
软件构造第一章总结 一、软件构造多维度视图1.1 从三个维度看软件系统的构成 按阶段划分:build-time(构造阶段)和run-time(运行阶段) 按动态划分:moment(时刻)和period(时期) 按层次划分:code(
2020-03-08
  目录