上午在园子里乱逛,看了不少小伙伴们分享的接口测试方面的知识,大家所叙述到的一些经验或多或少,我也曾遇到过,突然意识到知识的点滴积累是多么的重要,我记得我最早接触接口测试的时候,就是只在浏览器里人工测试单个接口的返回结果,后来用python的unittest自己写测试框架,和现在大多数小伙伴们的方法差不多,测试用例也是存放在excle表中,这对于单人测试来说都还ok,但是如果是多人协同测试时,问题就出来了,因为按目录存放在不同的excle表中的测试用例,维护起来比较麻烦,而且不便于多人查询或共同维护测试用例。所以之前的公司老大给我们推荐了一个工具——fitnesse,它是用wiki方式在界面上管理测试用例,驱动后台脚本进行测试,因为测试用例界面是个wiki地址,可以很方便的和大家一起协同工作,而且用例查询和维护起来都方便的多。
还是api接口测试,年前我从不同的角度分别写了几个不同的demo,虽然现在大多数用fitnesse的人都是用的slim引擎,但是因为java helloworld水平的我一直用的python,找不到合适的支持slim的python插件,所以还是用的fitnesse的fit引擎,用PyFIT支持起来。
对于api接口功能测试,我个人认为需要关注的有这几个方面:接口状态,响应时间,字段格式,返回数据,想起来之前面试阿里时,提到接口测试,他问了我很多http协议类的,具体问题记不清了,大概是几次握手交互那类的,额,不知道是不是关注点不同的缘故,他问得几个关于接口测试的问题我都没用过,其实到现在我还是有些疑惑的,不知道是他理解的接口测试和我理解的有偏差,不过也许是我涉猎的领域太窄,对于接口应该怎么充分测试我是比较怀疑了,也经常会在网上翻阅关于接口测试的文档,但是感觉收效甚微,嗯,对于未知领域的探索仍在继续,对于已知领域的小果子,拿出来和小伙伴一起分享下吧
所测接口:api接口,返回结果json格式
所用工具:fitnesse,fit引擎,python
第一种方式:
测试思想:在页面上初始化测试数据,将接口的返回结果按每个字段逐一填写期望结果,和接口的实际结果比较
测试数据准备:在界面上利用sql语句初始化测试数据,然后在测试用例页面included 该页面
测试用例:将json各个字段拆开填写到测试用例表格中,用ColumnFixture,测试用例格式如下
baseurl:接口基本不变的部分,这一部分可以在表格外参数化然后传值到表格里,account和password是接口的两个输入参数,带?标识的是要验证的结果,将json返回结果的每个字段都拆开填写在表格中。
后台脚本:
class LoginTest(ColumnFixture): _typeDict = { "description":"String", "BaseUrl":"String", "account":"String", "password":"String", "status":"Int", "retMsg":"String", "token":"String", "uClen":"Int", "uCuserId":"Int", "uCamount":"String", } def __init__(self): ColumnFixture.__init__(self) self.account='' self.password='' self.BaseUrl='' self.jsonData='' self.ret='' def getRes(self):
url=self.BaseUrl+"account="+self.account+"&password="+self.password tmp=res.fetch_res(url) result = json.loads(tmp) return result def retMsg(self): self.jsonData=self.getRes() self.ret=self.jsonData["ret"] result=str(self.jsonData["ret"])+self.jsonData["msg"] return result def status(self): url=self.BaseUrl+"account="+self.account+"&password="+self.password result=res.fetch_status(url) return result def token(self): result='' if self.ret==1: result= self.jsonData["data"]["token"] return result def uClen(self): result='' if self.ret==1: result=len(self.jsonData["data"]["userCapital"]) return result def uCuserId(self): result='' if self.ret==1: result=self.jsonData["data"]["userCapital"]["userId"] return result def uCamount(self): result='' if self.ret==1: result=str(self.jsonData["data"]["userCapital"]["amount"]) return result
优点:测试脚本结构简单,测试用例格式清晰,缺点:如果接口返回层级或字段较多时,不便于测试用例维护,需要初始化测试数据并清除增加的数据,部分动态字段(比如creattime)无法准确校验
第二种方式:
测试思想:在已有数据库基础上,无需每次添加测试数据,在测试脚本中根据需求用sql语句检索出对应字段的数据,作为期望结果,和接口的实际结果比较
测试数据:已有数据库基础上
测试脚本部分示例:
def retMsg(self): if self.status==200: self.jsonData=self.getRes() isUserSql="SELECT * FROM hcm_user WHERE NAME LIKE \'"+self.account+"\' AND PASSWORD LIKE \'"+self.password+ "\'AND TYPE=0" self.isUser=db.queryDb(isUserSql) self.ret=self.jsonData["ret"] result=str(self.jsonData["ret"])+self.jsonData["msg"] return result else: return '' def securityStatusCheck(self): symbol="=" list=['userId','userName','emailStatus','mobileStatus','realNameAuthStatus','autoTransfer','trusteeshipAccountStatus'] dataJson=[] dataCase=[] if self.isUser: sql="SELECT a.id,a.`name`,IF(a.email!='',1,0),IF(a.`mobile`,1,0),b.`yeepay_account_status`,b.auto_transfer,b.`yeepay_account_status` FROM hcm_user a,hcm_user_auth b WHERE NAME LIKE '"+self.account+"' AND a.id=b.user_id" data=db.queryDb(sql) if data: for i in range (0,len(list)): dataCase.append(list[i]+symbol+str(data[0][i])) if self.ret==1: tmp=self.jsonData["data"]["securityStatus"] for i in range (0,len(list)): dataJson.append(list[i]+symbol+str(tmp[list[i]])) result=Check(dataJson, dataCase) return result
测试结果:
突然发现给自己写优缺点好二啊,反正就是上面两种都没有满足老大们的要求,他们希望我能写一个通用的框架,让没有任何编码能力的人也能进行接口测试,即只需要前台编写测试用例,不用管后台脚本就能进行测试,于是乎有了下面第三种方式
第三种方式:
测试思想:满足老大们的要求,不用编写任何脚本即可进行接口测试
测试数据:固定初始化好的数据库
测试用例:
其中,firsturl是被依赖的登录接口,url是所测接口,blackLIst是希望过滤de返回字段的黑名单(比如ordeId,每次都是变化的,无法准确校验,添加到黑名单中即可不对其校验),data是期望结果,因为所测接口需要先登录然后保持session,才能返回正常结果,所以此处采用的是fit的Actionfixture
测试脚本示例:
from fit.Fixture import Fixture import urllib2,cookielib,urllib import module,json import sys reload(sys) sys.setdefaultencoding('UTF-8') class ActionTest(Fixture): _typeDict = {} def __init__(self):#初始化参数 Fixture.__init__(self) self.__firstUrl = '' #< Private attributes (Python convention). self.__url = '' self.__parameter = '' self.__blackList='' self.__data='' self.res='' self.status='' self.expectedList='' self.actualList='' self.test='' _typeDict["firstUrl"] = "String" def firstUrl(self, s): self.__firstUrl = s _typeDict["url"] = "String" def url(self, s): self.__url = s _typeDict["parameter"] = "Dict" def parameter(self, s): self.__parameter = s _typeDict["blackList"] = "List" def blackList(self, s): self.__blackList = s _typeDict["data"] = "String" def data(self, s): self.__data = s _typeDict["do"] = "Default" #< AUTO-DETECT: None = void def do(self):#访问接口并保存结果 cookie=cookielib.CookieJar() opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) try: req=opener.open(self.__firstUrl) self.status=req.code except urllib2.HTTPError, e: self.status= e.code if self.status==200: for cj in cookie: if cj.name=='JSESSIONID': session= cj.value req=urllib2.Request(self.__url) data=urllib.urlencode(self.__parameter) try: tmp = opener.open(req,data) self.status=tmp.code except urllib2.HTTPError, e: self.status=e.code if self.status==200: self.res= tmp.read() else: self.res='{"status":"no 200"}' else: self.res='{"loginStatus":"no 200"}' _typeDict["status"] = "Int" def status(self): return self.status _typeDict["expect"] = "String" def expect(self):#调用module函数比较测试结果 self.expectedList=[] self.actualList=[] module.resultList(self.__blackList,self.__data, self.res, self.expectedList, self.actualList)#比较后将结果存放到输出数组中 result=module.outPut(self.expectedList) #tmp=unicode(self.__data, 'utf-8') #return str(self.actualList return result _typeDict["actual"] = "String" def actual(self):#调用module函数比较测试结果 result=module.outPut(self.actualList) return result
测试结果: