目录
⊙MBT 是什么?
⊙PRE/POST 模型是什么?
⊙如何建立PRE/POST模型?
⊙OCL是什么?
⊙OCL怎么建立PRE/POST模型?
⊙PRE/POST模型MBT 实践
一、MBT是什么
MBT中文名称为基于模型的测试, 基于模型的测试属于软件测试领域的一种测试方法。按照此方法,测试用例可以完全或部分的利用模型自动产生。以上所说的模型通常是指对被测系统(SUT,system under test)某些(通常是功能性的)方面的描述。
模型一般都是对被测系统(SUT, system under test)预期行为动作的抽象描述。这些测试用例的集合就是我们平时所称的抽象测试套件(abstracttest suite). 抽象测试套件不可以直接执行于需测试的系统,因为他们不在同一抽象级别。
下图为MBT整体的流程
MBT核心点在于:
模型直接决定后面两个要素如何进行。
MBT中模型通常有下列几种
其中转换和前置后置条件模型是最常用的模型。对于面向数据的系统(例如网络接口),前置后置条件模型是比较适用的,转换模型例如有限转态机模型(FSM)则比较适合面向交互系统(例如UI系统) .
本文主要讲述PRE/POST模型在网络接口测试MBT中的使用和实践。
二、PRE/POST 模型是什么
网络接口通常是基于一定的契约/约定来执行的.
如我们有一个类Person, 它有一个setAge方法
Class Person {
Public:
int setAge(int newAge);
Private:
Int age;
String name;
}
下面三类条件,它们一起构成一个契约。
对于(接口)方法来说,先决条件是那些在(接口)方法被调用之前就必须满足的条件,它保证方法可以按照预期来运行。如上面setAge方法,先决条件是输入大于0等等。
对于(接口)方法来说,后置条件是那些(接口)方法被调用之后所要保持的条件(只要先决条件满足)。如上面setAge方法,后置条件是age的大小为输入的值。
对象的不变量是那些只要在(接口)方法被调用之前它的先决条件被满足就始终为真的数据。方法调用前和调用后都必须为真. 例如setAge方法,name值应该是不变的.
三、如何建立PRE/POST模型
可以使用OCL 语言来描述 PRE/POST模型。
四、OCL是什么
OCL 全称为Object Constraint Language,中文是对象约束语言。 OCL是UML的一部分。在很多情况下,仅有UML并不能准确地描述系统
OCL可用于指定对象的不变量和方法的输入(前置)和输出(后置条件),使得UML类图更为精确。
例如下图的UML并没有办法描述下列限制:
汽车所有人的年龄限制;
汽车所有人的个数限制;
要求一个人至少要有一辆黑色的车子;
上面的限制可以使用OCL来表述:
如:
汽车所有人年龄必须大于18岁:
contextVehicle
inv: self. owner. age >= 18 (不变量)
一个人所有汽车都应该是黑色的
context Person
inv: self.fleet->forAll(v| v.colour = #black) (不变量)
五、OCL怎么建立PRE/POST模型
OCL不仅仅支持不变量,也支持pre/post
还是上面的UML,我们现在要求 setAge 输入是非负数,并且age属性会被设置为此输入的数值,OCL的表达式为
contextPerson::setAge(newAge:int)
pre: newAge>= 0(pre前置条件)
post: self.age= newAge(post 后置条件)
对于网络接口来说,我们可以使用OCL来描述接口方法的输入(前置条件)和输出(后置条件),还有不变量。
以服务FlightService的子服务updateData 为例, 该服务的功能是更新航班信息,其输入参数包括: 用户名UserName、密码Password和航班信息FlightInfo。
输出参数的数据类型为ReturnCode.
此服务的部分契约为:
⊙我们可以用OCL 来描述上述的约束:
输入的航班信息里面的到达时间晚于离开时间, 则返回错误码:(ARR_BEFORE_DEP_TIME = 1)
context FlightService::updateData(username:UserName,password:Password, flightInfo:FlightInfo)
pre: flightInfo.depTime> flightInfo.arrTime
post: self.result = 1
输入的航班信息里面的到达城市和离开城市不同,则返回正确码 ( NO_ERROR = 0)
context FlightService::updateData(username:UserName,password:Password, flightInfo:FlightInfo)
pre: flightInfo.depCity!= flightInfo.arrCity
post: self.result= 0
输入的航班信息里面的到达时间早于离开时间, 则返回正确码 ( NO_ERROR = 0)
context FlightService::updateData(username:UserName,password:Password, flightInfo:FlightInfo)
pre:flightInfo.depTime < flightInfo.arrTime
post: self. result= 0
六、PRE/POST模型MBT
对于很多网络接口, 大部分情况都是采用自动化的工具来发送请求并且得到响应消息, 但是对于每个用例, 用户需要手动的指定该接口的输入和输出. 还需要制定用例通过相应的校验条件. 新写一个用例的时间都是相同的.
该方法的缺点:
如果采用MBT方式的话, 测试人员只需要制定该接口的OCL约束, 指明该接口的输入, 输出和不变量的表达式, MBT生成器可以自动生成测试数据(覆盖率较高), 并且执行测试用例, 在执行完成后, 还能自动根据OCL来校验相应 接口的输入和输出是否满足前置和后置条件从而判断用例是否通过, 效率大大提升.
概括起来的优点为:
1. 用例自动生成,大大减少了书写用例的时间.
2. 用例覆盖率会比较高(采用模糊器生成每个域的随机列表,然后可以采用组合测试 +OCL来生生数据).
3. 用例自动校验结果,减少用例写校验的时间.
4. 将测试时间提前: 可以在开发制定完接口就可以进行接口的MBT测试, 帮助完成测试左移.
使用OCL结合PRE/POST模型来建立MBT体系如下,测试人员输入的仅仅是该接口的OCL描述(即对该接口建立模型),剩下的全是自动化生成和执行的.
用户可以采用解析OCL来直接生成测试用例,在我们实际的测试中,我们采用了相应的模糊器工具结合OCL来生成相应的测试数据,然后调用执行器得到相应接口的响应消息,最后调用OCL解释来判断该响应是否满足我们执行的约束条件。
以前面的网络服务FlightService的子服务updateData为例, 该服务的功能是更新航班信息,其输入参数包括: 用户名UserName、密码Password和航班信息FlightInfo。
输出参数的数据类型为ReturnCode.
下面约束为: 机票信息中的出发时间要与航班信息的出发时间一致
context
FlightService::updateData(username:UserName,password:Password,flightInfo:FlightInfo)
pre:self.flightInfo.ticketInfo.ticketTime.depTime=self.flightInfo.depTime
post:self.result = 0
对于上面约束, 我们在实际的MBT采用的是下面语法的约束 (我们把响应消息转换为json格式后,它的typeName自动设置为 RspMsg,相应的输入设置为属性reqMsg的值)
context RspMsg
inv: if self.reqMsg.flightInfo.ticketInfo.ticketTime.depTime = self.reqMsg.flightInfo.depTime then self.result = 0 else self.result<> 0
我们将接口的输入和输出作为数据,来校验这个规则。
对于下面数据(满足机票信息中的出发时间要与航班信息的出发时间一致的约束):
OCL对上面的规则校验的结果应该是true
对于下面数据(不满足机票信息中的出发时间要与航班信息的出发时间一致的约束):
对于下面的数据
OCL校验的结果应该是返回false
OCL的工具有很多,如基于EMF的Dresdanocl工具,EMF 本身是比较成熟的建模工具,其中类可以采用Ecore/uml/xds等方式来定义,实例支持xml, xmi等,约束可采用OCL或者java本身来定义, Dresdantocl 是一个提供OCL语法验证的基于EMF的工具。
我们在实践中采用的OCL校验工具是https://github.com/SteKoe/ocl.js,可以根据自己的需求进行扩展和定制,语法相对比较简单也比较轻量。用户可以根据自己的实际情况来选择。