MBT框架的实现需要用到2种技术:参数组合技术与代码生成技术。
参数组合技术:抽象出测试场景的参数,并对参数做等价类及边界值分析后,利用迪卡尔乘积对多类参数进行组合,并过滤掉不需要组合,这里多半是用例建模时需要考虑。框架需要提供的是组合算法与过滤算法,用户在使用这些算法的比较易懂、实用。
代码生成技术:框架应能执行模型代码,执行模型后能生成可执行的测试代码,这有点类似病毒,代码执行后,能产生出与自己等价的代码。举例如下:
原始模型代码:
from MTest import TestCaseBase, Model, Scenario, Action, Assert, Logger @Action(DespFormat="Called with {0}, {1}") def JustReturnArg1(arg1, arg2): if arg1 == 1: return arg2 return arg1 @Model() class SimpleModel(TestCaseBase): @Scenario(Param={ 'p1':[1, 2], 'p2':['a', 'b'] }, Where={'combine':['p1','p2'],'strategy':'add'}) def TestJustReturnSelf(self): Logger.Step('Call JustReturnSelf and Validate') p1 = self.Param['p1'] p2 = self.Param['p2'] rsp = JustReturnArg1(p1, p2) Assert.AreEqual(p1, rsp, 'p1 check') if __name__ == '__main__': import sys from MTestLauncher import MTestLoader MTestLoader.Run(None, False, "MTestRunConfig.xml", [sys.argv[0]], "mtest") |
在执行后,生成3个测试用例
@Model(TestModule="") def SimpleModel(TestCaseBase): @Scenario(Param={'p1': 1, 'p2': 'a'}) def TestJustReturnSelf_p1(self): Logger.Step("Call JustReturnSelf and Validate") rsp1 = JustReturnArg1(self.Param["p1"], self.Param["p2"]) Assert.AreEqual(self.Param["p1"], rsp1, "p1 check") @Scenario(Param={'p1': 1, 'p2': 'b'}) def TestJustReturnSelf1_p2(self): Logger.Step("Call JustReturnSelf and Validate") rsp1 = JustReturnArg1(self.Param["p1"], self.Param["p2"]) Assert.AreEqual(self.Param["p1"], rsp1, "p1 check") @Scenario(Param={'p1': 2, 'p2': 'a'}) def TestJustReturnSelf2_p1(self): Logger.Step("Call JustReturnSelf and Validate") rsp1 = JustReturnArg1(self.Param["p1"], self.Param["p2"]) Assert.AreEqual(self.Param["p1"], rsp1, "p1 check") |
以SpecExplore为例:
<!--[if !supportLists]-->1. <!--[endif]-->开发出建模脚本,通过解析脚本结合接口定义生成出测试用例。但这样的伪代码的开发成本和使用难道都比较大,没有语法检查,几乎靠经验摸索着使用。
<!--[if !supportLists]-->2. <!--[endif]-->利用对象的属性值变化作为模型转换的状态。对于初学者,来定位一个模型逻辑问题,难度和要求都是很高的。
<!--[if !supportLists]-->3. <!--[endif]-->在执行模型时,根据在脚本中对模型接口定义好的枚举参数进行组合并且根据模型实现确定每一个参数。
在MTest中,建模与一般的写测试用例的方式是一致,先进行参数组合,再执行模型,模型通过一个全局属性获取参数或者直接传给测试用例。为使能生成代码,在执行模型过程需要记住模型的执行路径,拦截住执行每个接口函数调用的入参和期待的输出。其中,期待的输出是从对接口的校验中获取。因此,拦截接口函数调用的入参与输出是关键的技术。
从语言层面看,要拦截函数的入参和输出:
<!--[if !supportLists]-->1. <!--[endif]-->Java里有spring可以做些拦截,但创建对象的必须从spring的容器中获取。即使通过这种拦截能达到上面的效果,将其引入测试,配置和使用会异常复杂。
<!--[if !supportLists]-->2. <!--[endif]-->C#跟Java类似,C++就更不可能了。
只能求助脚本语言了,Python语言的一个特性刚好可以做到这一点:获取Python对象的属性或函数都是通过__getattribute__来做到的,在定义类时覆盖这个函数,返回包装好这个函数执行的代理函数,就可以拦截函数的入参、执行和给出一个模拟的输出。以一个例子说明如下:
class A: def __getattribute__(self, name): print("Here, can return a delegate for function: %s, not just itself" % name) return object.__getattribute__(self, name) def bFun(self): print('b Called') A().bFun() |
输出:
Here, can return a delegate for function: bFun, not just itself
b Called
实际上,在mtest模式执行模型时,MTest中对bFun的代理函数并不会真实的去执行bFun,而是直接返会了一个动态结果DynamicResult,交给Assert.AreEqual去校验,而Assert.AreEqual也不会被真正的执行,而是仅仅是记住这个动态结果的期望值。这就知道了函数执行时的入参和输出。因此,只要是引用到动态结果的函数都应该是能被代理掉的。否则,这个函数得到将是一个未知的动态结果对象。从另外一个角度看,这实际上是对函数调用的一次Mock!
另注:MTest里标记为@Action的函数和所有以Logger和Assert开始的函数都是能被代理掉的;能生成Python的代码,引入不同的代码生成技术,同样可以生成Java,C#,Ruby,JS、JMeter性能测试脚本的代码