为什么好的测试设计是不可避免的?
许多书、博客文章等都说,要么你花大量的钱和时间进行测试以达到高质量,要么你省钱但你的软件质量仍然很差。这是不对的。 现实情况是,在软件开发生命周期(SDLC)中,有两个重要的成本要素,团队可以修改:测试和错误修复。它们都不是线性的。
当我们想找到更多的bug时,测试的成本会增加。在最开始的时候,这种增加或多或少是线性的,也就是说,执行两倍的测试用例意味着检测到的错误的数量将增加一倍。然而,随着测试用例的增加,成本变得非线性。原因是在一定程度上,我们需要通过组合的方法来组合(和测试)输入域的元素。例如,我们首先测试一些分区,然后是它们的边界,然后是边界值的一对或三对,等等。假设测试用例的数量在项目中是不可忽略的。没有理由设计和执行过多的测试,因为即使增加了许多测试,我们也无法发现大量的bug。
另一个因素是缺陷纠正(bug修复)的成本。在软件生命周期中,我们越晚发现一个错误,其修正的成本就越高。根据一些出版物,修正成本是 "时间上的指数",但显然超过了线性,很可能呈现多项式增长。因此,如果我们能在生命周期的早期发现故障,那么总的修正成本就可以大大降低。正如你在下面看到的,把这两个因素放在一起考虑,总成本有一个最佳值。(这些数字不是真实的,只是证明了这个概念)

这意味着,减少测试和检测成本不是一个好的策略,因为你应该把这两个因素一起考虑。主要的问题是--这个成本最优的结果是可以接受的生产代码质量吗?这不是一个简单的问题。假设你应用一个低效的测试设计。在这种情况下,由于测试成本高,未被发现的错误数量仍然很高,即使你达到了最佳状态,软件质量也可能无法接受。相反,如果你应用非常有效的测试设计技术,最佳状态将被移到一个点上,在这个点上,检测到的错误比率要高得多,导致质量可以接受,见下图。

因此,高效的测试设计技术对于降低SDLC成本,同时使代码质量变得可接受是非常必要的。然而,不同的功能需要不同水平的测试设计。让我们考虑两个已实现的功能,并假设第一个功能的代码更复杂(在某些方面)。因此,它包含有更高概率的故障。如果我们以相同的费用和彻底性对两者进行测试,那么在测试之后,第一个代码中会留下更多的故障。这意味着,总的纠正成本会更高。
我们可以用类似的论据来考虑风险。假设我们有两个具有相同复杂性和测试成本的功能(故障分布相似),但第一个功能的风险更大。例如,这个功能被更频繁地使用,或者这个功能被用于 "关键 "流程中。因此,在生命周期中,更多的bug将在这个功能中被检测和修复,纠正成本更高。请注意,我们在这里说的是对错误的检测,而不是对错误的存在。
粗略的说,如果测试工作/成本保持不变,更复杂的代码和更高的风险会提高修复错误的成本。由于这种额外的错误修复发生在生命周期的后期,发布时的代码质量会更差。因此,一个风险更大或更复杂的功能必须以更彻底的方式进行测试,也就是以更高的测试成本。为了达到最佳的总成本,这种较高的测试成本是不可避免的,见下图,其中HRF是高风险的功能,LRF是低风险的功能。

对于传统的风险分析,有两个因素,影响和概率。在这里,我们可以把复杂性作为第三个因素。
通过将风险的大小映射到功能上,我们可以使测试设计接近于最优。对于风险较大的代码,我们应该选择更强的测试设计技术。一个好的选择是使用相同的测试设计技术,但有不同的测试选择标准。例如,如果你使用状态转换测试或动作状态测试设计技术,你可以选择最小的测试选择标准,对低风险的功能进行全转换测试,或对风险较大的功能进行更强的全转换对标准。如果有必要,你可以使用更强的标准。
假设你确实为风险较高的特征选择了更强的测试设计技术,但你如何检查你是否接近了最佳状态?这并不是一件容易的事。唯一的方法是收集尽可能多的数据。例如:让我们分别测量你对功能的测试工作。先应用一个比较简单的标准,然后扩展它,测量这两种努力。然后测量检测到的bug的修复成本。最后,测量发布后检测到的bug的修复成本。现在你可以通过近似于较简单的标准(对于较强的标准所检测到的bug)的修复成本来计算应用较弱和较强标准的测试和修复成本之和。最后,考虑风险。如果较简单的标准的总成本较低,那么你可以为这个风险值选择这个标准。使用这些数据,你可以为给定的风险选择适当的测试设计水平。
哪些测试应该是自动化的?
没有人会把所有的测试用例都自动化。有些功能不是很重要,或者改变功能的概率接近于零(假设这个功能在静态分析意义上是独立于其他功能的)。相对于单一的手动测试执行,测试自动化成本是很高的。将这些功能自动化是合理的吗?当然不是。也可能有一些功能只会被改变一次或两次。如果测试自动化的成本是单一人工测试的五倍,那么自动化就毫无价值。
另一方面,如果一个bug会阻碍软件的使用,或者该功能会被多次修改,或者受到许多经常改变的功能的影响,那么测试自动化是必须的,这是毫无疑问的。
同样,我们可以做一个风险分析。如果影响非常大或很高,那么即使不会发生变化,我们也应该进行自动化测试。原因是,即使一个功能保持不变,其他一些功能可能会影响它。如果这些 "影响者 "的功能正在发生变化,未修改的可能会出问题。问题是如何计算风险。很明显,影响是一个关键因素。概率是另一个要考虑的因素。然而,代替复杂性,我们应该考虑另一种类型的概率,即变化的概率。
因此,风险分析发生一次,所有四个风险项目(影响、概率、复杂性和变化概率)都应该由团队决定。你可以使用著名的风险扑克牌。根据风险,你可以决定哪些功能要被自动化。如果你想进行不同强度的回归测试,风险分析也有帮助。例如,你可以在每次修改代码后执行一些高风险的功能测试,而另一些风险较低的功能则每周执行一次。
最后一步是验证,以改善流程。在这里你可以验证功能的自动化是否合理。你知道在自动化测试执行过程中检测到的bug,与这些bug相关的bug修复成本,你衡量了测试自动化成本,你有关于发布后检测到的bug平均修复成本的历史数据。基于这些数据,你可以计算出自动化是否合理。如果不是,你可以微调你的基于风险的策略。
测试应该如何自动化?
很好!我们有一个好的测试设计,我们知道什么要自动化。唯一要做的是测试自动化本身。乍一看,测试很容易实现自动化。测试用例由用户行为和系统响应组成。在这两种情况下,你应该确定被测试的UI对象。对于任何测试自动化框架,解决方案是类似的。对于动作,你使用一个选择器/定位器和一个关键词。这里是一个使用Cypress的例子。
cy.get("#add Coke").click()
这里#add Coke选择了添加可乐的'+'按钮,而click()是点击这个按钮的关键字。对于验证来说,解决方案也很相似。
cy.get('#birthday').should('have.value', '11/01/1983')
你可以非常快速地编写测试代码,然而,有一个重要的问题:DRY,即不要重复自己。DRY "是软件开发的一个原则,旨在减少软件模式的重复"。由于可能有更多的测试案例包含点击添加可乐的按钮,同样的语句会被重复。在维护过程中,如果测试在改变,你应该在很多地方改变它。
为了避免这种情况,建议使用页面对象模型(POM)。页面对象模型是一种设计模式,页面对象与自动化测试脚本分开。你应该为应用程序中的每个页面创建单独的类。然后为每个动作和响应创建单独的方法,同时选择器也被分开。例如,这里是添加可乐的POM版本。
const addCokeSelector = "#add Coke";
class Example {
addCoke() {
cy.get(addCokeSelector).click();
}
}
it('add coke', () => {
const example = new Example();
cy.visit('https://site_of_add_coke)
example.addCoke();
})
现在,如果选择器或动作代码需要修改,你可以在一个地方进行修改。很好,我们完成了。哎呀,有一个问题:POM代码比原来的大得多。不是一个单一的语句,而是有一个选择器的变量声明,有一个动作/响应的方法,还有一个对该方法的调用。粗略地说,为了保持DRY,我们将代码大小增加了三倍。然而。
- 一些动作/响应从未被修改。
- 有些动作/响应是在一个测试案例中,而且只有一次。在这种情况下,修改只发生在一个地方。
你可能认为 "没问题,让我们再次使用风险分析"。不幸的是,在这里我们应该单独分析每个动作/反应,这是很昂贵的。 现在可以做什么呢? 幸运的是,有一个很好的解决方案:基于模型的测试!
基于模型的测试(MBT)是一种固有的涉及DRY解决方案的技术。MBT的本质是,我们不需要一个接一个地手动创建测试用例,而是创建一个适当的测试模型,MBT工具可以根据适当的测试选择标准生成测试用例。该模型可以是文本的或图形的。模型是基于测试设计技术的。例如,状态图是做状态转换测试的表示。 这里是著名的ATM认证的状态图。

为了满足所有转换对的标准,"插入有效的卡 "这个动作涉及三次。在模型中,这个动作只发生一次。这意味着这个动作只实现了一次,在有任何修改的情况下,你应该在一个地方修改它。模型显然比模型所代表的对象更紧凑。 我的经验告诉我,文本模型由最简单(非POM)的测试代码的三分之一组成。因此,POM版本大约比模型大9倍。在另一篇博客中,我将比较不同的MBT方法,但每一种方法都比使用原始POM要好。
使用MBT的另一个好处是,测试设计和测试执行自动化是合并的。我们非正式地证明,先进的测试设计是必须的。做一个单独的测试设计,然后一个一个地写测试是完全多余的。制作一个模型,然后生成测试是一个快速有效的解决方案。维护也是有效的,因为你永远不会接触代码,只有模型。测试代码是在每次修改模型后生成的。
这意味着,同时使用MBT和测试执行框架是最好的方法,可以获得高的软件质量和较低的SDLC成本。
总结
关于软件测试有很多误解。我在这里表明,使用有效的测试设计不仅能带来更好的质量,还能降低SDLC的总成本。我试图回答哪些测试用例应该被自动化的问题。最后,我表明使用MBT可以使测试自动化工作更有效率。不要扔掉你的测试自动化框架,而是用测试设计自动化工具来扩展它。在另一篇博客中,我将展示如何做到这一点。
我希望我回答了标题中提出的所有问题。