三、测试自动化

实现单元测试和基于准则的测试的前提是测试自动化
测试自动化就是将测试实例化后加载到可执行的测试脚本中的过程。下面是其完整定义。
测试自动化: 使用软件来控制测试用例的执行,实际输出和预期输出的比较、先验条件的设置,以及其他的测试控制和测试报告功能。

软件测试成本高而且耗费巨大人力,所以软件测试的一个重要目标就是尽可能地实现自动化。测试自动化不仅可以降低测试地成本,而且可以减少人为地错误,同时使回归测试变得更加容易。我们只需要一个按键,回归测试中地测试用例集就可以反复地运行下去。

软件工程师有时候需要区分核心任务辅助任务。核心任务直接用来解决问题,而辅助任务斌不是解决问题必须要做的。
例如,编译Java类就是一个经典的辅助任务。这是因为虽然编译是执行Java类所需要的,但编译本身并不对类的行为产生任何影响。相对而言,在Java类中决定用哪个方法来定义数据抽象则是核心任务。通常辅助任务可以实现自动化,核心任务则不行。软件测试可能会比软件开发的其他方面包含更多的辅助任务。
维护测试脚本,重新执行测试用例,比较实际结果和预期输出都是常见的并在平时花费测试工程师大量时间的辅助任务。
自动化辅助任务可以在多个方面帮助测试工程师。
第一、自动化辅助任务可以消除单调乏味的苦差,使测试工程师对工作更加满意。
第二、使用自动化解放了测试工程师,使他们去关注测试中有意思和有挑战的部分,比如测试设计这个核心任务。
第三、自动化可以使测试用例运行千百遍,而我们无需对每天甚至每小时都在运行的测试环境加以额外的操作。
第四、自动化可以帮助减少因疏忽造成的错误,例如当使用新的测试预期结果时忘记更新其相关的所有文件。
第五、自动化可以消除由测试者个人能力的高低所导致的测试质量上的差异。

3.1 软件可测性

软件可测性用来估量测试揭示故障的可能性。
可测性: 为了评判测试准测是否达标,系统或组件在测试准则建立和测试用例性能提升方面所能提供的便利程度。
两个常见的实际问题在很大程度上决定了软件的可测性:如何向软件提供测试数据和如何观察测试执行的结果。
软件可观察性: 观察程序行为的难易程度,程序行为包括输出,还有程序对运行环境和其他软件、硬件的影响。
软件可控性: 向程序提供所需输入的难易程度。程序输入包括数据值、操作和行为。

许多可观察性和和可控性的问题都是用仿真来解决的,仿真是使用额外的软件来绕过与测试交互的硬件和软件组件。其他通常具有低可观察性和可控性的软件类型有基于组件的软件、分布式软件和网络应用。

3.2 测试用例的构成

测试用例是由多个部分组成并且有明确的结构。
测试用例值: 在待测软件上完成测试执行所需的输入值。
测试用例值是测试设计师用来满足需求而产生的程序输入。虽然测试用例值决定了测试的质量,但是只有测试用例值并不够。出了测试用例值,执行测试用例还需要其他输入,这些输入可能取决于测试的来源,它们可能师命令行输入、用户输入或是带有参数赋值的软件方法。
前缀值: 将待测软件置于合适状态以接受测试用例值的必要输入。
后缀值: 测试用例值发送之后,待测软件仍然需要的输入。后缀值还可以细分为两种类型。
验证值: 查看测试用例值结果所需的值。
退出值: 终止程序或使程序回到一个稳定的状态所需的值或命令行输入
一旦测试执行终止,测试用例需要判定测试结果是否正确,或者说是否符合预期。这有时被称为测试预言问题。测试语言判定一个测试用例是否通过或失败。因此测试用例包括软件正常运行时所应产生的后果。
预期结果: 当软件的行为符合预期时,软件在测试用例中应产生的结果。
测试用例时各种组件(测试用例值、前缀值、后缀值和预期结果)的组合。当上下文没有歧义时,会使用传统用法“测试用例” 来替换测试用例值。
测试用例: 测试用例包括必要的测试用例值、前缀值、后缀值和预期结果,以便完整地执行和评估待测软件。
测试集: 测试集就是测试用例地集合。
完成测试用例执行地步骤是:加载测试用例值,运行软件、获得结果、比较实际结果和预期结果,以及为测试工程师准备一份清晰地报告。
可执行地测试脚本: 处于一种可以在待测软件上自动运行和生成报告地形式地测试用例。

3.3 测试自动化框架

测试框架: 支持测试自动化的假设、概念和工具的集合。
测试框架为测试脚本提供了标准的设计,还应该包括对测试驱动的支持。
测试驱动可以在软件上反复运行测试集合中的每一个测试用例。如果待测的软件组件不是独立的(即一个方法、类或是其他组件),那么测试驱动必须提供主方法来运行软件,测试驱动还应该比较实际执行的结果和预期结果(来自测试用例),然后向测试者汇报结果。
测试驱动最简单的形式就是一个类中的main方法。高效的程序员经常在每个类中都包含一个main方法,这个方法包括对这个类进行简单测试的语句。对于一个典型类,main测试驱动创建一些类的实例对象,调用存值方法来改变对象中数值,再调用取值方法获得验证的相关用值。

JUnit测试框架已经包括了上述时间。Junit提供了灵活的类库和API用来开发测试驱动。
绝大部分的测试自动化框架都支持:
1、评估预期结果的断言
2、在测试用例间分享共同的测试数据
3、方便组织测试集合和运行测试用例
4、从命令行或图形界面运行测试

3.3.1 JUnit测试框架
JUnit脚本可以作为独立的Java程序(从命令行)或是在集成开发环境(IDE)如Eclipse中运行。JUnit可以用来测试一个完整的类、类的一部分(比如一个方法或一些有交互的方法)或是类对象之间的交互。就是说JUnit主要用于单元测试和集成测试,而非系统测试。
JUnit将每个测试用例集嵌入到一个测试方法,这些测试方法再整合到测试类中。测试类包含两个部分:
1、测试方法的集合
2、在运行每个测试用例前设置程序状态(前缀值)的方法和在运行每个测试用例之后更新状态(后缀值)的方法。
我们使用junit.framework.assert类中的方法来编写测试类。每个测试方法检查一个条件(断言),并且向测试执行者汇报测试是否成功。断言是预期结果和测试预言在JUnit测试用例中的实现,之后测试执行者向用户汇报结果。所有的断言方法返回void。
1、assertTrue(boolean):这是最简单的断言。从理论上来说,有关程序变量的任意判断都可以最终使用这个断言来实现。
2、assertTrue(String, boolean):这个断言为测试者提供了更多信息。如果这个断言返回真,则忽略字符串参数。如果断言返回非真,这个字符串将被发送给测试工程师。这个字符串应该简明扼要地总结失败。
3、fail(String):很多新的测试工程师对这个断言感到困惑。但当执行一段代码导致失败时,字符串向测试工程师提供一段总结。这个fail经常被用来测试程序的异常行为。

这些都是所谓“基于状态的”测试,这种测试就是指的是将待测单元产生的值和已知的正确值进行比较。对此作为补充的还有一种“基于交互的”测试。
JUnit使用测试夹具这个概念来表示测试用例的状态,而测试用例的状态由待测软件中关键变量的当前值来决定。当相同的对象和变量被用于多个测试用例时,测试夹具尤其有效。测试夹具可以用来控制前缀值(初始化)和后缀值(重置状态)。这使得不同的测试用例无须共享状态就可以使用相同的对象,每个测试用例都独立于其他测试用例运行。
下面是一个使用JUnit的简单例子:

public class Calc
{
	static public int add (int a, int b)
	{
		return a + b;
	}
}
import org.junit.*;
import static org.junit.Assert.*;
public class CalcTest
{
	@Test public void testAdd()
	{
		assertEquals(5, Calc.add(2, 3));
	}
}

下面是一个更复杂的例子:
其中包括Java泛型和测试异常,包括了测试夹具方法和前三个测试方法。
@Before方法实现测试前缀值的部分,它创建了一个新的List对象,使测试对象处于正常的初始化状态。
@After 方法实现测试后缀值的部分,它将对象引用指向null来重置测试对象的状态。
其中前三个测试用例预期结果为NullPointerException的测试用例。
第一个测试用例使用了fail语句,预计运行测试用例会抛出NullPointerException并捕捉到这个异常,如果没有异常抛出或是抛出一个不同的异常抛出,测试用例将会执行fail语句,然后测试失败,不需要assert语句。
第二个测试用例阐释了测试异常行为的一种替代方法。具体来说,可以在@Test注解中加入预期的异常类。这类无需fail和assert。
第三个测试用例列表中只包含一个null元素的情况容易被忽略,这个测试用例让Min方法在变量result初始化之后强制其抛出NullPointerException。
第四五个测试用例是测试异常。
第六七个测试用例是处理正常的行为。

import java.util.*;

public class Min
{
    
    public static <T extends Comparable<? super T>> T min (List<? extends T> list)
    {
        if (list.size() == 0){
            throw new IllegalArgumentException("Min.min");
        }

        Iterator<? extends T> itr = list.iterator();
        T result = itr.next();

        if (result == null)throw new NullPointerException("Min.min");
        while (itr.hasNext())
        {
            T comp = itr.next();
            if (comp.compareTo(result) < 0)
            {
                result = comp;
            }
        }
        return  result;
    }
}
import org.junit.*;
import static org.junit.Assert.*;
import java.util.*;

public class MinTest
{
    private List<String> list;

    //设置方法(在每个测试方法之前调用)
    @Before
    public void setUp()
    {
        list = new ArrayList<String>();
    }

    //析构方法(在每个测试方法之后调用)
    @After
    public void tearDown()
    {
        list = null;
    }

    @Test
    public void testForNullList()
    {
        list = null;
        try {
            Min.min(list);
        } catch (NullPointerException e) {
            return;
        }
        fail("NullPointerException expected");
    }

    @Test(expected = NullPointerException.class)
    public void testForNullElement()
    {
        list.add(null);
        list.add("cat");
        Min.min(list);
    }

    @Test (expected = NullPointerException.class)
    public void testForSoloNullElement()
    {
        list.add(null);
        Min.min(list);
    }

    @Test (expected = ClassCastException.class)
    @SuppressWarnings("unchecked")
    public void testMutuallyIncomparable()
    {
        List list = new ArrayList();
        list.add("cat");
        list.add("dog");
        list.add(1);
        Min.min(list);
    }

    @Test (expected = IllegalArgumentException.class)
    public void testEmptyList()
    {
        Min.min(list);
    }

    @Test
    public void testSingleElement()
    {
        list.add("cat");
        Object obj = Min.min(list);
        assertTrue("Single Element List", obj.equals("cat"));
    }

    @Test
    public void testDoubleElement()
    {
        list.add("dog");
        list.add("cat");
        Object obj = Min.min(list);
        assertTrue("Double Element List", obj.equals("cat"));
    }
}

3.3.2 数据驱动测试

有时候我们将不同的输入值和预期结果加载到相同的测试方法中,然后执行多次。一个更好的方法是只写一次测试用例,然后在另一个表里提供测试数据。这个方法通常称为数据驱动测试

你可能感兴趣的:(软件测试基础,软件测试)