是否有任何正式的/数学的软件测试理论?


12

谷歌搜索“软件测试理论”似乎只是给出了软词义的理论。我没有找到任何可以归类为数学,信息理论或其他科学领域意义上的理论的东西。

我正在寻找的东西可以形式化什么是测试,使用的概念,什么是测试用例,测试某物的可行性,测试某物的实用性,应该测试某物的程度,形式的定义/解释代码覆盖率等

更新:另外,从直觉上讲,我不确定形式验证与我所要求的内容之间是否存在联系,但是显然存在某种联系。


1
软件测试非常有价值(例如,将单元测试付诸实践),但是其理论总是会存在一些漏洞。考虑这个经典的例子:double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }我从我的数学老师那里学到了。这段代码恰好有一个漏洞,仅凭黑盒测试无法自动发现。在数学中没有这样的漏洞。在微积分中,如果单边限制相等,则可以封闭孔。
rwong 2011年

4
这可能是你在找什么部分- en.wikipedia.org/wiki/Formal_verification
enderland

1
我赞同@enderland的建议。测试方法的严格程度无关紧要;有些错误仍然会从裂缝中溜走,并且随着您在测试中覆盖更多的代码,发现新错误的成本也会增加。这可能就是为什么没有人会麻烦地将测试的概念形式化的原因-一种“启发式”方法几乎不需要培训就可以很好地工作。
Doval 2015年

从那以后,我就通过依赖类型熟悉了形式验证的领域,并且我可以完全同意@Doval和enderland。
埃里克·卡普伦

1
@rwong我想您暗示分子和分母都等于零的可能性。部分原因是此功能的设计不良。在谈论数学形式验证时,您需要根据正确的数据类型随意组合函数,但要遵循形式规则。在此示例中,您将必须使用除法函数(a,b)=>a/b,该函数需要使用溢出值进行扩展才能正确组合。
Dmitri Zaitsev

Answers:



5

我无法指出一个好的在线资源(有关这些主题的英文Wikipedia文章往往是可改进的),但是我可以总结一下我听到的一个演讲,其中也涵盖了基本的测试理论。

测试模式

有不同种类的测试,例如单元测试集成测试。单元测试断言,一条连贯的代码段(函数,类,模块)按预期的独立工作,而集成测试断言,多个这样的代码段可以正确地协同工作。

测试用例是一种已知的环境,其中例如通过使用特定的测试输入或通过模拟其他类来执行一段代码。然后将代码的行为与预期行为(例如特定的返回值)进行比较。

测试只能证明存在错误,而不能证明所有错误。测试为程序的正确性设定了上限

代码覆盖率

为了定义代码覆盖率度量,可以将源代码转换为控制流程图,其中每个节点都包含代码的线性段。控制仅在每个块的末尾在这些节点之间流动,并且始终是有条件的(如果有条件,则转到节点A,否则转到节点B)。该图具有一个起点和一个终点。

  • 使用此图,语句覆盖率是所有访问的节点与所有节点的比率。完整的语句覆盖范围不足以进行全面测试。
  • 分支覆盖率是CFG中节点之间所有访问的边缘与所有边缘的比率。这不足以测试循环。
  • 路径覆盖率是所有访问路径与所有路径的比率,其中路径是从起点到终点的任意边序列。问题在于,使用循环时,可能有无限数量的路径,因此无法实际测试完整的路径覆盖率。

因此,检查条件覆盖率通常很有用。

  • 简单条件覆盖范围内,每个原子条件分别为真和为假–但这不能保证完整的语句覆盖范围。
  • 多重条件覆盖,原子条件采取的所有组合truefalse。这意味着完全的分支机构覆盖范围,但是相当昂贵。该程序可能具有排除某些组合的其他约束。该技术非常适用于获取分支覆盖,可以找到无效代码,但无法找到由于错误条件而导致的错误
  • 在“ 最小多重条件覆盖率”中,每个原子和复合条件都为真和假。它仍然意味着完整的分支机构覆盖范围。它是多个条件覆盖范围的子集,但需要较少的测试用例。

使用条件覆盖构建测试输入时,应考虑短路。例如,

function foo(A, B) {
  if (A && B) x()
  else        y()
}

需要使用foo(false, whatever)foo(true, false)和进行测试,以foo(true, true)实现最小的多重条件覆盖。

如果您的对象可能处于多种状态,那么测试所有类似于控制流的状态转换似乎是明智的。

有一些更复杂的覆盖率指标,但是它们通常与此处介绍的指标相似。

这些是白盒的测试方法,并且可以部分地自动化。请注意,单元测试套件应以任何选择的指标为目标,以具有较高的代码覆盖率为目标,但并非总是100%可行。在必须将故障注入到特定位置的情况下,测试异常处理尤其困难。

功能测试

然后有一些功能测试,它们通过将实现视为黑盒来断言该代码符合规范。这样的测试对于单元测试和集成测试都非常有用。因为不可能用所有可能的输入数据进行测试(例如,使用所有可能的字符串测试字符串长度),所以将输入(和输出)分组为等效类很有用-如果length("foo")正确,foo("bar")则也可能工作。对于输入和输出对等类别之间的每种可能组合,至少要选择一个代表性输入并进行测试。

一个应该另外测试

  • 边缘的情况下length("")foo("x")length(longer_than_INT_MAX)
  • 语言允许的值,但函数的合同不允许的值length(null),以及
  • 可能的垃圾数据length("null byte in \x00 the middle")...

使用数字表示测试0, ±1, ±x, MAX, MIN, ±∞, NaN,而使用浮点比较测试两个相邻的浮点数。另外,可以从等效类中选择随机测试值。为了简化调试,值得记录使用的种子…

非功能测试:负载测试,压力测试

一款软件具有非功能性要求,这些功能也必须经过测试。这些包括在定义的边界(负载测试)以及超出边界的测试(压力测试)。对于计算机游戏,这可能是在负载测试中声明每秒的最小帧数。可能会给网站进行压力测试,以观察预期的响应时间是两倍多的预期访问者正在殴打服务器。这样的测试不仅与整个系统相关,而且与单个实体相关–哈希表如何随着一百万个条目而降级?

其他类型的测试是对场景进行模拟的整个系统测试,或者是证明已满足开发合同的验收测试。

非测试方法

评论

有一些非测试技术可用于质量保证。例如演练,正式代码审查或结对编程。尽管某些零件可以自动化(例如使用短绒),但这些零件通常很耗时。但是,由经验丰富的程序员进行的代码审查会发现错误的比率很高,并且在无法进行自动测试的设计过程中特别有价值。

当代码审查如此出色时,为什么我们还要编写测试?测试套件的最大优势在于,它们可以(大部分)自动运行,因此对于回归测试非常有用。

正式验证

正式验证可以证明代码的某些属性。手动验证对于关键部分来说最可行,而对整个程序则不太可行。证明为程序的正确性设定了下限。证明可以在一定程度上实现自动化,例如通过静态类型检查器。

某些不变量可以通过使用assert语句进行显式检查。


所有这些技术都有自己的位置,并且是互补的。TDD预先编写了功能测试,但是一旦实现代码,就可以通过其覆盖率指标来判断这些测试。

编写可测试的代码意味着编写可单独测试的小型代码单元(具有适当粒度的帮助功能,单一职责原则)。每个函数采用的参数越少越好。这样的代码也适合于通过例如依赖注入来插入模拟对象。


2
我很欣赏这个详尽的答案,但是我担心它与我的要求几乎没有任何关系:)但是,幸运的是,programmers.stackexchange.com / questions / 78675 /… 似乎与我所得到的接近目标。
埃里克·卡普伦

这是很棒的东西。你能推荐任何书籍或东西吗?
Marcin 2014年

4

也许“基于规范的测试”也可以回答您的问题。检查这些测试模块(我还没有使用过)。它们要求您编写数学表达式来指定测试值集,而不是使用所选的单个数据值编写单元测试。

测试:: Lectrotest

正如作者所说,此Perl模块的灵感来自Haskell的Quick-Check模块。此页面上还有更多链接,其中一些链接已失效。



2

使用了一些数学方程式,但这取决于您使用的软件测试的类型。例如,关键故障假设假设故障几乎不是两个或多个同时故障的乘积。下式为:f = 4n + 1。f =为给定数量的变量(n) + 1计算测试用例数量的函数,其中所有变量均取标称值的常数1的加法。

另一种需要数学方程式的测试是“稳健性测试”,它是在测试过程中测试测试用例的稳健性或正确性。在此测试中,您将输入合法输入范围内的变量(干净的测试用例),并在输入范围外输入变量(脏的测试用例)。您将使用以下数学方程式:f = 6n + 16n表示每个变量必须假定6个不同的值,而其他值假定标称值。* + 1 *表示常数1的加法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.