动态取值_深入浅出之动态测试方法

b8e0fc28915f98f1738ea26f33d6b4cc.gif

相较于,静态测试方法是不需要实际执行代码去发现潜在代码错误的方法,我们今天讨论的动态测试方法,则是要通过实际执行代码去发现潜在代码错误的测试方法。


正如我在分享《不破不立:掌握代码级测试的基本理念与方法》这个主题时,将动态测试方法进一步划分为人工动态方法和自动动态方法,今天这次关于动态测试方法的分享,我也会从这两个方面展开。


由于自动动态方法并不能理解代码逻辑,所以仅仅被用于发现异常、崩溃和超时这类“有特征”的错误,而对于代码逻辑功能的测试,主要还是要依靠人工动态方法。

b8e0fc28915f98f1738ea26f33d6b4cc.gif

人工动态方法

人工动态方法,可以真正检测代码的业务逻辑功能,其关注点是“什么样的输入,执行了什么代码,产生了什么样的输出”,主要用于发现算法错误和部分算法错误,是最主要的代码级测试手段。

从人工动态方法的定义中,你可以很清楚地看出:代码级测试的人工动态测试方法,其实就是单元测试所采用的方法。所以,下面的分享,我会从单元测试方法的角度展开。

如果有一些代码基础,那么你在学习单元测试框架或者工具时,会感觉单元测试很简单啊,一点都不难:无非就是用驱动代码去调用被测函数,并根据代码的功能逻辑选择必要的输入数据的组合,然后验证执行被测函数后得到的结果是否符合预期。 但是,一旦要在实际项目中开展单元测试时,你会发现有很多实际的问题需要解决。

接下来,我将和你分享单元测试中三个最主要的难点:

  1. 单元测试用例“输入参数”的复杂性;
  2. 单元测试用例“预期输出”的复杂性;
  3. 关联依赖的代码不可用。

单元测试用例“输入参数”的复杂性

提到“输入参数”的复杂性,你应该已经记起了,我在前面的分享中提到过:如果你认为单元测试的输入参数只有被测函数的输入参数的话,那你就把事情想得过于简单了。

其实,这也是源于我们在学习单元测试框架时,单元测试用例的输入数据一般都是被测函数的输入参数,所以我们的第一印象会觉得单元测试其实很简单。

但是到了实际项目时,你会发现单元测试太复杂了,因为测试用例设计时需要考虑的“输入参数”已经完全超乎想象了。

第一,被测试函数的输入参数

这是最典型,也是最好理解的单元测试输入数据类型。假如你的被测函数是下面这段代码中的形式,那么函数输入参数 a 和 b 的不同取值以及取值的组合就构成了单元测试的输入数据。

int someFunc(int a, int b) { … }

复制代码

第二,被测试函数内部需要读取的全局静态变量

如果被测函数内部使用了该函数作用域以外的变量,那么这个变量也是被测函数的输入参数。

下面这段代码中,被测函数 Func_SUT 的内部实现中使用了全局变量 someGlobalVariable,并且会根据 someGlobalVariable 的取值去执行 FuncA() 和 FuncB() 这不同的代码分支。

在做单元测试时,为了能够覆盖这两个分支,你就必须构造 someGlobalVariable 的不同取值,那么自然而然,这个 someGlobalVariable 就成为了被测函数的输入参数。

所以,在这段代码中,单元测试的输入参数不仅包括 Func_SUT 函数的输入参数 a,还包括全局变量 someGlobalVariable。

bool someGlobalVariable = true; void Func_SUT(int a) { ... if(someGlobalVariable == true) { FuncA(); } else { FuncB(); } ... }

复制代码

第三,被测试函数内部需要读取的类成员变量

如果你能理解“被测函数内部需要读取的全局静态变量”是单元测试的输入参数,那么“被测试函数内部需要读取的类成员变量”也是单元测试的输入参数就不难理解了。因为,类成员变量对被测试函数来讲,也可以看做是全局变量。

我们一起看一段代码。这段代码中,变量 someClassVariable 是类 someClass 的成员变量,类的成员函数 Func_SUT 是被测函数。Func_SUT 函数,根据 someClassVariable 的取值不同,会执行两个不同的代码分支。

同样地,单元测试想要覆盖这两个分支,就必须提供 someClassVariable 的不同取值,所以 someClassVariable 对于被测函数 Func_SUT 来说也是输入参数。

class someClass{ ... bool someClassVariable = true; ... void Func_SUT(int a) { ...if(someClassVariable == true) { FuncA(); } else { FuncB(); } ... } ... }

复制代码

第四,函数内部调用子函数获得的数据

“函数内部调用子函数获得的数据”也是单元测试的输入数据,从字面上可能不太好理解,那我就通过一段代码,和你详细说说这是怎么回事吧。

void Func_SUT(int a) { bool toggle = FuncX(a); if(toggle == true) { FuncA(); } else {FuncB(); } }

复制代码

函数 Func_SUT 是被测函数,它的内部调用了函数 FuncX,函数 FuncX 的返回值是 bool 类型,并且赋值给了内部变量 toggle,之后的代码会根据变量 toggle 的取值来决定执行哪个代码分支。

那么,从输入数据的角度来看,函数 FuncX 的调用为被测函数 Func_SUT 提供了数据,也就是这里的变量 toggle,后续代码逻辑会根据变量 toggle 的取值执行不同的分支。所以,从这个角度来看,被测函数内部调用子函数获得的数据也是单元测试的输入参数。

这里还有一个小细节,被测函数 Func_SUT 的输入参数 a,在内部实现上只是传递给了内部调用的函数 FuncX,而并没有在其他地方被使用,我们把这类用于传递给子函数的输入参数称为“间接输入参数”。

这里需要注意的是,有些情况下“间接输入参数”反而不是输入参数。

就以这段代码为例,如果我们发现通过变量 a 的取值很难控制 FuncX 的返回值(也就是说,当通过间接输入参数的取值去控制内部调用函数的取值,以达到控制代码内部执行路径比较困难)时,我们会直接对 FuncX(a) 打桩,用桩代码来控制函数 FuncX 返回的是 true 还是 false。

这样一来,原本的变量 a 其实就没有任何作用了。那么,此时变量 a 虽然是被测函数的输入参数,但却并不是单元测试的输入参数。

第五,函数内部调用子函数改写的数据

理解了前面几种单元测试的输入参数类型后,“函数内部调用子函数改写的数据”也是单元测试中被测函数的输入参数就好解释了。

比如,当被测函数内部调用的子函数改写了全局变量或者类的成员变量,而这个被改写的全局变量或者类的成员变量又会在被测函数内部被使用,那么“函数内部调用子函数改写的数据”也就成为了被测函数的输入参数了。

第六,嵌入式系统中,在中断调用中改写的数据

嵌入式系统中,在中断调用中改写的数据有时候也会成为被测函数的输入参数,这和“函数内部调用子函数改写的数据也是单元测试中的输入参数”类似,在某些中断事件发生并执行中断函数时,中断函数很可能会改写某个寄存器的值,但是被测函数的后续代码还要基于这个寄存器的值进行分支判断,那么这个被中断调用改写的数据也就成了被测函数的输入参数。

其实在实际工程项目中,除了这六种输入参数,还有很多输入参数。在这里,我详细分析这六种输入参数的目的,一来是帮你理解到底什么样的数据是单元测试的输入数据,二来也是希望你可以从本质上认识单元测试的输入参数,那么在以后遇到相关问题时,你也可以做到触类旁通,不会再踌躇无措。

理解了“输入参数”的复杂性,接下来我们再一起看看“预期输出”的复杂性表现在哪些方面。

相较于,静态测试方法是不需要实际执行代码去发现潜在代码错误的方法,我今天要和你讨论的动态测试方法,则是要通过实际执行代码去发现潜在代码错误的测试方法。

由于自动动态方法并不能理解代码逻辑,所以仅仅被用于发现异常、崩溃和超时这类“有特征”的错误,而对于代码逻辑功能的测试,主要还是要依靠人工动态方法。

总结

代码级测试的动态测试方法,可以分为人工动态测试方法和自动动态测试方法。其中人工动态测试方式,是最常用的代码级测试方法,也是我们在进行单元测试时采用的方法。

人工动态方法,也就是单元测试方法,通常看似简单,但在实际的工程实践中会遇到很多困难,总结来看这些困难可以概括为三大方面:

  1. 单元测试用例“输入参数”的复杂性,表现在“输入参数”不是简单的函数输入参数。本质上讲,任何能够影响代码执行路径的参数,都是被测函数的输入参数。
  2. 单元测试用例“预期输出”的复杂性,主要表现在“预期输出”应该包括被测函数执行完成后所改写的所有数据。
  3. 关联依赖的代码不可用,需要我们采用桩代码来模拟不可用的代码,并通过打桩补齐未定义部分。

而自动动态方法,需要重点讨论的是:如何实现边界测试用例的自动生成。解决这个问题最简单直接的方法是,根据被测函数的输入参数生成可能的边界值。

思考题

除了我们一起讨论的这些单元测试的难点,还有复杂数据初始化、函数内部不可控子函数的调用、间接输入参数的估算等难点。你在单元测试中是否遇到过这些问题呢,又是如何解决的?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值