HIT Reading Thoughts

01 Testing

前言:软件测试是伴随着软件的产生而产生的。早期的软件开发过程中软件规模都很小、复杂程度低,软件开发的过程混乱无序、相当随意,测试的含义比较狭窄,开发人员将测试等同于“调试”,目的是纠正软件中已经知道的故障,常常由开发人员自己完成这部分的工作。目前测试不单纯是一个发现错误的过程,而且将测试作为软件质量保证(SQA)的主要职能,包含软件质量评价的内容。

验证

测试是称为验证的更常规过程的示例。验证的目的是发现程序中的问题,从而增加您对程序正确性的信心。验证包括:

  1. 程序的正式推理,通常称为验证。验证构造程序正确的正式证明。手工验证是繁琐的,自动化工具支持验证仍然是一个活跃的研究领域。但是,程序的小、关键部分可以正式验证,例如操作系统中的调度程序、虚拟机中的字节码解释器或操作系统中的文件系统。
  2. 代码评审。让别人仔细阅读你的代码,并非正式地谈论它,可以是发现错误的好方法。这就像让别人校对你写的文章一样。我们将在下一读中讨论有关代码评审的更多。
  3. 测试。在精心挑选的输入上运行程序并检查结果。

即使拥有最佳的验证,也很难在软件中实现完美的质量。以下是一些典型的剩余缺陷率(软件发货后遗留下来的错误)每个 kloc(一千行源代码):
1 - 10 缺陷/kloc:典型的行业软件。
0.1 - 1 缺陷/kloc:高质量验证。Java 库可能会达到这种正确性级别。
0.01 - 0.1 缺陷/kloc:最佳、安全关键型验证。美国宇航局和像Praxis这样的公司可以达到这个水平。
对于大型系统来说,这可能令人沮丧。例如,如果您已发运了一百万行典型的行业源代码(1 个缺陷/kloc),这意味着您错过了 1000 个 Bug!

测试优先编程

尽早和经常测试。当有大量未经验证的代码时,不要离开测试,直到结束。将测试保留到最后只会使调试更长、更痛苦,因为 Bug 可能位于代码的任意位置。在开发代码时测试代码会更加令人愉快。
在测试优先编程中,您甚至在编写任何代码之前编写测试。单个函数的开发按以下顺序进行:
1.为函数编写规范。
2.编写执行规范的测试。
3.编写实际代码。一旦代码通过您编写的测试,您就完成了。
规范描述函数的输入和输出行为:它提供了参数的类型和它们上的任何其他约束(例如,参数必须是非负的)。它还提供返回值的类型以及返回值与输入的关系。
ps: 编写测试是了解规范的好方法。规范也可能是错误 - 不正确,不完整,模棱两可,缺少角案例。在浪费时间编写错误规范的实现之前,尝试编写测试可以尽早发现这些问题。

通过分区选择测试用例

在这里插入图片描述
1.创建一个好的测试套件是一个具有挑战性的和有趣的设计问题。我们希望选择一组足够小、可以快速运行但足够大以验证程序的测试用例。

2.为此,我们将输入空间划分为 子域每个子域由一组输入组成。综合起来,子域完全覆盖输入空间,因此每个输入至少位于一个子域中。然后,我们从每个子域中选择一个测试用例,这就是我们的测试套件。

3.子域背后的理念是将输入空间分区到程序具有类似行为的类似输入集中。然后,我们使用每组一个代表。此方法通过选择不同的测试用例,并强制测试探索随机测试可能无法访问的部分输入空间,从而充分利用有限的测试资源。

4.如果需要确保我们的测试将探索输出空间的不同部分,我们还可以将输出空间划分为子域(程序具有类似行为的类似输出)。大多数时候,分区输入空间就足够了。

例子: BigInteger.multiply()
BigInteger是内置于 Java 库中的类,可以表示任何大小的整数,与基元类型不同,并且范围有限。BigInteger 有一个将两个 BigInteger 值相乘的方法:int long multiply

在这里插入图片描述
例如:
在这里插入图片描述
此示例显示,即使方法的声明中显式显示一个参数,实际上也是两个参数的函数:调用方法的对象(在上面的示例中)和在括号中传递的参数(在此示例中)。在 Python 中,接收方法调用的对象将显式命名为方法声明中调用的参数。在 Java 中,您不会在参数中提及接收对象,而是调用 它而不是 。multiplyabselfthisself

因此,我们应该考虑作为一个函数,采用两个输入,每个输入的类型,并产生一个类型的输出:multiplyBigIntegerBigInteger

multiply : BigInteger × BigInteger → BigInteger

因此,我们有一个二维输入空间,由所有整数对(a,b)组成。现在,让我们分区它。考虑乘法的工作原理,我们可能会从这些分区开始:

  • a 和 b 都是正数
  • a 和 b 都是负数
  • a 为正数,b 为负数
  • a 为负数,b 为正数
  • 还有一些乘法特殊情况, 我们应该检查:0,1和-1。
  • a 或 b 为 0、1 或 -1
    最后,作为一个可疑的测试人员,我们可能会怀疑 BigInteger 的实现者可能尝试通过使用或内部(如果可能)使其更快,并且只有在值太大时才会回落到昂贵的常规表示形式(如数字列表)。因此,我们当然也应该尝试非常大的整数,大于最大的整数。
    int long long
  • a 或 b 很小
  • a 或 b 的绝对值大于 Java 中可能的最大基元整数,约为 2^63 。
    让我们将所有这些观测结果整合到整个空间的简单分区中。我们将选择并独立从: (a,b) a b
    主要是 0 、1、 -1 、 小正整数、 小负整数 、 巨大的正整数 、 巨大的负整数
    因此,这将生成 7 × 7 = 49 个分区,完全覆盖整数对的空间。

要生成测试套件,我们将从网格的每个方块中选择一个任意对 (a,b),例如:
(a,b) = (-3, 25) 覆盖(小负,小正数)
(a,b) = (0, 30) 覆盖 (0, 小正极)
(a,b) = (2=100, 1) 覆盖(大正,1)
等。
在这里插入图片描述

使用JUnit进行自动化单元测试

JUnit是一个被广泛采用的 Java 单元测试库,我们将在 6.031 中大量使用它。JUnit 单元测试编写为注释前面的方法。单元测试方法通常包含对要测试的模块的一个或多个调用,然后使用断言方法(如 ) 和 检查结果。
@Test assertEquals assertTrue assertFalse
例如:

@Test
public void testALessThanB() {
    assertEquals(2, Math.max(1, 2));
}

@Test
public void testBothEqual() {
    assertEquals(9, Math.max(9, 9));
}

@Test
public void testAGreaterThanB() {
    assertEquals(-5. Math.max(-5, -6));
}

请注意,参数的顺序很重要。第一个参数应该是测试希望看到的预期结果(通常是常量)。第二个参数是实际结果,代码实际执行什么。如果切换它们,则 JUnit 将在测试失败时生成令人困惑的错误消息。JUnit 支持的所有断言都一致遵循此顺序:预期第一,实际第二。assertEquals

如果测试方法中的断言失败,则该测试方法会立即返回,JUnit 会记录该测试的失败。测试类可以包含任意数量的方法,在使用 JUnit 运行测试类时,这些方法将独立运行。即使一个测试方法失败,其他测试方法仍将运行。@Test

自动测试和回归测试

自动测试

自动测试意味着运行测试并自动检查测试结果。测试驱动程序不应是一个交互式程序,它提示您输入并打印结果,以便您手动检查。相反,测试驱动程序应在固定测试用例上调用模块本身,并自动检查结果是否正确。测试驱动程序的结果应该是"所有测试正常"或"这些测试失败:…"一个好的测试框架(如 JUnit)可帮助您构建自动化测试套件。像 JUnit 这样的自动测试框架便于运行测试,但您仍必须自己提出良好的测试用例。自动生成测试是一个难题,仍然是计算机科学研究的一门学科。

回归测试

完成测试自动化后,在修改代码时重新运行测试非常重要。软件工程师从痛苦的经历中知道,对大型或复杂程序的任何更改都是危险的。无论您是修复另一个 Bug、添加新功能还是优化代码以使其更快,保留正确行为基线(即使只是几个测试)的自动测试套件都将节省您的熏肉。在更改代码时频繁运行测试可防止程序倒退- 修复新 Bug 或添加新功能时引入其他 Bug。每次更改后运行所有测试称为回归测试。

总结

  • 测试优先编程。在编写代码之前编写测试。
  • 用于系统地选择测试用例的分区和边界。
  • 用于填写测试套件的白盒测试和语句覆盖率。
  • 尽可能隔离地测试每个模块。
  • 自动回归测试,防止 Bug 返回
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页