测试运行

分散测试

James McCaffrey

下载代码示例

image: James McCaffrey
在本月的专栏中,我将向您介绍一种软件测试技术,我将这种技术称为分散测试。分散测试的核心思想是有时可以从生成合格结果的现有测试用例自动生成新的测试用例数据。

虽然分散测试技术在大多数软件测试情形中不适用,但是一旦它适用,便可以大大提高您的测试工作效率。它是所有主要测试技术中最鲜为人知的技术之一,根据我的经验,这部分原因可能在于它是一种间隙技术。

在提供分散测试的示例之前,我先解释一下这种方法背后的动机。测试用例通常由测试用例 ID 、一组输入(一个或多个)和预期结果组成。例如,向量 {001, 2, 3, 5} 可表示 Sum 函数的测试用例,其中 ID = 001、输入 = 2 和 3,而预期结果 = 5。将测试用例输入发送到测试中的系统来生成实际结果,然后将这一实际结果与预期结果进行比较,以确定测试用例的结果是否合格。

在许多软件测试情形中,确定测试用例的预期结果是一个困难且耗时的部分。例如,假设您要测试一个基本数学函数,该函数可用于计算两个速率输入的调和平均数。30.0 千米每小时 (kph) 和 60.0 kph 的平均数不是 (30.0 + 60.0) / 2 = 45.0 kph,而是 30.0 和 60.0 的调和平均数,即 1 / ((1/30.0 + 1/60.0) / 2) = 40.0 kph。计算此函数的数百个预期结果的过程枯燥、耗时且易于出错。

确定测试用例预期结果的困难是软件测试中的基本概念,有时将其称为测试准则问题。实际上,软件测试的必杀技之一是搜寻能够自动确定测试用例的技术。因此,进行分散测试的动机是:如果您可以设法自动生成新的测试用例数据,则您将绕过测试过程的耗时部分,并且能够更全面地测试您的系统。

原则上,通过分散测试自动生成测试用例数据意义重大,但如何实现这个目标呢?解释分散测试的最佳方法是举例。看一下图 1

image: Diffusion Testing Demo

图 1 分散测试演示

现在,我要测试一个函数 Choose(n,k),此函数返回从 n 项中选择 k 项的方法的数目(不考虑顺序)。在这个简化的示例中,我有三个现有测试用例。第一个测试用例的输入 n = 8,k = 3,预期结果是 56。使用测试工具执行了第一个测试用例(生成了合格的结果)后,我使用了分散测试来自动生成新的测试用例,其中输入 n = 9,k = 3,预期结果是 84。太巧妙了!请注意,由于测试用例 002 生成的结果不合格,因此我没有生成一个新的分散测试用例。

但是,新的测试用例是如何从现有测试用例生成的呢?对于 Choose(n,k) 函数,由数学知识可知:Choose(n+1,k) = Choose(n,k) * (n+1) / (n-k+1)。换句话说,新输入和旧返回值之间的关系是已知的。图 2 显示了我用于从现有测试用例生成分散测试用例的函数。code.msdn.microsoft.com/mag201103TestRun 上提供了生成了图 1 中所示的输出的整个程序。

图 2 生成新的测试用例

static string CreateDiffusedTestCase(string existingTestCase)

{

  // Assumes input format is CaseID:N:K:Expected

  string[] tokens = existingTestCase.Split(':');



  string oldTestCase = tokens[0];

  int oldN = int.Parse(tokens[1]);

  int oldK = int.Parse(tokens[2]);

  long oldExpected = long.Parse(tokens[3]);



  string newTestCase = oldTestCase + "-diffused";

  int newN = oldN + 1;

  int newK = oldK;

  long newExpected = (oldExpected * (oldN + 1)) / (oldN - oldK + 1);



  return newTestCase + ":" + newN + ":" + newK + ":" + newExpected;

}

我再另外举几个例子来帮助您更清楚地理解这个概念。 假设您要测试可计算三角正弦和余弦的函数。 您可能记得 sin 2t = 2 * sin t * cos t。 如果您的测试用例生成的结果对于某个输入的正弦和余弦来说是合格的,那么您便可以使用分散测试为正弦函数生成一个新的测试用例。

分散测试不是魔术。 假设您要测试一个函数,该函数接受某种产品 ID,搜索 SQL 数据库,并在相应产品有存货时返回 true,没有存货时返回 false。 由于不同的输入和结果之间没有任何关系,因此,您不能在这种情形中使用分散测试。 在这一方面,分散测试与其他形式的测试(例如,边界条件测试和二元组合测试)是相似的:分散测试是一种只在某些情况下适用的技术。

现在,我们来看分散测试的另一个例子。 假设您编写了一个函数 Gauss(z),该函数接受标准正态 z 值,并从负无穷到 z 返回标准正态(钟形曲线)分布下的区域。 例如,Gauss(-1.645) = 0.0500、Gauss(1.645) = 0.9500 和 Gauss(0) = 0.5000。 使用分散测试的一种方法是,注意 Gauss 的单调性,并且注意对于负无穷到 2.5 区间内的任何 z 值,Gauss(z + 0.1) 的结果必须大于 Gauss(z)。 使用分散测试的另一种方法是,注意 Gauss 的对称性,并且注意对于小于 0.0 的任何 z 值,Gauss(-z) = 1.0 - Gauss(z)。

我举的例子说明了分散测试适用的三种最常见但绝非唯一的情形。 第一种情形是,您要测试一个可被定义为重复关系的数学函数。 第二种情形是,您要测试一个具有某种单调关系的函数。 第三种情形是,您要测试一个具有某种对称关系的函数。 如果您要测试一个函数,并且在该函数中切换输入值的顺序不会更改返回值(如 Sum(x,y) 这种函数),则此时就出现了一种相关的测试形式,但这种测试并不是分散测试。

数学函数是可受益于分散测试的最常见的测试组件类型,因为此类函数大多都具有重复性、单调性或对称性 — 不过您还应该注意其他情况。 涉及重复关系的数学函数尤其适合分散测试,因为您通常可以从现有测试用例生成多个新的测试用例。 在图 1 的演示中,测试用例 001(n = 8,k = 3,预期结果 = 56)生成了一个新的分散测试用例(n = 9,k = 3,预期结果 = 84)。 这个新的测试用例可用于生成另一个测试用例(n = 10,k = 3,预期结果 = 120),如果生成的这个测试用例合格,那么它就可用于再生成一个新的测试用例,依此类推。

在作总结之前,我再讲一个与为不同软件测试技术和原则命名相关的小问题。 我已经将本专栏中介绍的技术命名为分散测试,因为现有测试用例通过分散来创建新用例。 我也可以把这种技术称为自适应测试或自动生成测试等等。 重要的不是名称,而是名称所代表的技术。

在许多研究领域(包括软件测试)中,一些自称为专家的人为一些常识性技术赋予了名称,并试图说服对这些领域不熟悉的人,让这些不熟悉的人相信名称本身在某种程度上具有一定的重要性。 他们之所以这样做,通常是因为他们想通过就这个了不起的新名称煞有介事地在会厅进行演讲,来直接销售培训服务或间接推销咨询服务。 值得一提的是“探索性测试”和“上下文测试流派”,不过除此之外还有其他许多词。 所以说,我们还是实事求是地理解“分散测试”这个词 — 它只是一个描述软件测试技术的名称,不过也可能对您的技术工具包起到了重要的补充作用,仅此而已。

James McCaffrey博士供职于 Volt Information Sciences, Inc.,在该公司他负责管理对华盛顿州雷蒙德市沃什湾 Microsoft 总部园区的软件工程师进行的技术培训。他参与过多项 Microsoft 产品的研发工作,包括 Internet Explorer 和 MSN Search。McCaffrey 博士是《.NET 软件测试自动化之道》(Apress, 2006) 的作者,您可通过以下电子邮箱地址与他联系:jammc@microsoft.com

衷心感谢以下技术专家对本文的审阅:Bj RollisonAlan Page