職稱論文寫作網(wǎng)站推廣普通話手抄報(bào)文字
上午在園子里亂逛,看了不少小伙伴們分享的接口測(cè)試方面的知識(shí),仔細(xì)想想,我做接口測(cè)試也有幾個(gè)年頭了,大家所敘述到的一些經(jīng)驗(yàn)或多或少,我也曾遇到過(guò),突然意識(shí)到知識(shí)的點(diǎn)滴積累是多么的重要,我記得我最早接觸接口測(cè)試的時(shí)候,就是只在瀏覽器里人工測(cè)試單個(gè)接口的返回結(jié)果,后來(lái)用python的unittest自己寫測(cè)試框架,和現(xiàn)在大多數(shù)小伙伴們的方法差不多,測(cè)試用例也是存放在excle表中,這對(duì)于單人測(cè)試來(lái)說(shuō)都還ok,但是如果是多人協(xié)同測(cè)試時(shí),問(wèn)題就出來(lái)了,因?yàn)榘茨夸洿娣旁诓煌膃xcle表中的測(cè)試用例,維護(hù)起來(lái)比較麻煩,而且不便于多人查詢或共同維護(hù)測(cè)試用例。所以之前的公司老大給我們推薦了一個(gè)工具——fitnesse,它是用wiki方式在界面上管理測(cè)試用例,驅(qū)動(dòng)后臺(tái)腳本進(jìn)行測(cè)試,因?yàn)闇y(cè)試用例界面是個(gè)wiki地址,可以很方便的和大家一起協(xié)同工作,而且用例查詢和維護(hù)起來(lái)都方便的多。
? ? ? 我在之前的公司用了將近一年這個(gè)工具,但是因?yàn)閯傂萃戤a(chǎn)假的自己工作狀態(tài)非常不好,其實(shí)并沒(méi)有在這方面很盡心,只到來(lái)了新公司后,才又一次重新學(xué)習(xí)使用起這個(gè)工具來(lái),還是api接口測(cè)試,年前我從不同的角度分別寫了幾個(gè)不同的demo,雖然現(xiàn)在大多數(shù)用fitnesse的人都是用的slim引擎,但是因?yàn)閖ava helloworld水平的我一直用的python,找不到合適的支持slim的python插件,所以還是用的fitnesse的fit引擎,用PyFIT支持起來(lái)。
? ? ?對(duì)于api接口功能測(cè)試,我個(gè)人認(rèn)為需要關(guān)注的有這幾個(gè)方面:接口狀態(tài),響應(yīng)時(shí)間,字段格式,返回?cái)?shù)據(jù),想起來(lái)之前面試阿里時(shí),提到接口測(cè)試,他問(wèn)了我很多http協(xié)議類的,具體問(wèn)題記不清了,大概是幾次握手交互那類的,額,不知道是不是關(guān)注點(diǎn)不同的緣故,他問(wèn)得幾個(gè)關(guān)于接口測(cè)試的問(wèn)題我都沒(méi)用過(guò),其實(shí)到現(xiàn)在我還是有些疑惑的,不知道是他理解的接口測(cè)試和我理解的有偏差,不過(guò)也許是我涉獵的領(lǐng)域太窄,對(duì)于接口應(yīng)該怎么充分測(cè)試我是比較懷疑了,也經(jīng)常會(huì)在網(wǎng)上翻閱關(guān)于接口測(cè)試的文檔,但是感覺(jué)收效甚微,嗯,對(duì)于未知領(lǐng)域的探索仍在繼續(xù),對(duì)于已知領(lǐng)域的小果子,拿出來(lái)和小伙伴一起分享下吧
? ? ?所測(cè)接口:api接口,返回結(jié)果json格式
? ? ?所用工具:fitnesse,fit引擎,python
第一種方式:
? ?測(cè)試思想:在頁(yè)面上初始化測(cè)試數(shù)據(jù),將接口的返回結(jié)果按每個(gè)字段逐一填寫期望結(jié)果,和接口的實(shí)際結(jié)果比較
? ?測(cè)試數(shù)據(jù)準(zhǔn)備:在界面上利用sql語(yǔ)句初始化測(cè)試數(shù)據(jù),然后在測(cè)試用例頁(yè)面included 該頁(yè)面
? 測(cè)試用例:將json各個(gè)字段拆開(kāi)填寫到測(cè)試用例表格中,用ColumnFixture,測(cè)試用例格式如下
baseurl:接口基本不變的部分,這一部分可以在表格外參數(shù)化然后傳值到表格里,account和password是接口的兩個(gè)輸入?yún)?shù),帶?標(biāo)識(shí)的是要驗(yàn)證的結(jié)果,將json返回結(jié)果的每個(gè)字段都拆開(kāi)填寫在表格中。
后臺(tái)腳本:
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.passwordtmp=res.fetch_res(url) result = json.loads(tmp)return resultdef 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.passwordresult=res.fetch_status(url)return result def token(self):result=''if self.ret==1:result= self.jsonData["data"]["token"]return resultdef uClen(self):result=''if self.ret==1:result=len(self.jsonData["data"]["userCapital"])return resultdef uCuserId(self):result=''if self.ret==1: result=self.jsonData["data"]["userCapital"]["userId"]return resultdef uCamount(self):result=''if self.ret==1:result=str(self.jsonData["data"]["userCapital"]["amount"])return result
優(yōu)點(diǎn):測(cè)試腳本結(jié)構(gòu)簡(jiǎn)單,測(cè)試用例格式清晰,缺點(diǎn):如果接口返回層級(jí)或字段較多時(shí),不便于測(cè)試用例維護(hù),需要初始化測(cè)試數(shù)據(jù)并清除增加的數(shù)據(jù),部分動(dòng)態(tài)字段(比如creattime)無(wú)法準(zhǔn)確校驗(yàn)
第二種方式:
測(cè)試思想:在已有數(shù)據(jù)庫(kù)基礎(chǔ)上,無(wú)需每次添加測(cè)試數(shù)據(jù),在測(cè)試腳本中根據(jù)需求用sql語(yǔ)句檢索出對(duì)應(yīng)字段的數(shù)據(jù),作為期望結(jié)果,和接口的實(shí)際結(jié)果比較
測(cè)試數(shù)據(jù):已有數(shù)據(jù)庫(kù)基礎(chǔ)上
測(cè)試用例:
?測(cè)試腳本部分示例:
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 resultelse: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
測(cè)試結(jié)果:
突然發(fā)現(xiàn)給自己寫優(yōu)缺點(diǎn)好二啊,反正就是上面兩種都沒(méi)有滿足老大們的要求,他們希望我能寫一個(gè)通用的框架,讓沒(méi)有任何編碼能力的人也能進(jìn)行接口測(cè)試,即只需要前臺(tái)編寫測(cè)試用例,不用管后臺(tái)腳本就能進(jìn)行測(cè)試,于是乎有了下面第三種方式
第三種方式:
測(cè)試思想:滿足老大們的要求,不用編寫任何腳本即可進(jìn)行接口測(cè)試
測(cè)試數(shù)據(jù):固定初始化好的數(shù)據(jù)庫(kù)
測(cè)試用例:
其中,firsturl是被依賴的登錄接口,url是所測(cè)接口,blackLIst是希望過(guò)濾de返回字段的黑名單(比如ordeId,每次都是變化的,無(wú)法準(zhǔn)確校驗(yàn),添加到黑名單中即可不對(duì)其校驗(yàn)),data是期望結(jié)果,因?yàn)樗鶞y(cè)接口需要先登錄然后保持session,才能返回正常結(jié)果,所以此處采用的是fit的Actionfixture測(cè)試腳本示例:
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):#初始化參數(shù)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 = voiddef do(self):#訪問(wèn)接口并保存結(jié)果cookie=cookielib.CookieJar()opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))try:req=opener.open(self.__firstUrl)self.status=req.codeexcept urllib2.HTTPError, e:self.status= e.codeif self.status==200:for cj in cookie:if cj.name=='JSESSIONID':session= cj.valuereq=urllib2.Request(self.__url)data=urllib.urlencode(self.__parameter)try:tmp = opener.open(req,data)self.status=tmp.codeexcept urllib2.HTTPError, e:self.status=e.codeif 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):#調(diào)用module函數(shù)比較測(cè)試結(jié)果self.expectedList=[]self.actualList=[]module.resultList(self.__blackList,self.__data, self.res, self.expectedList, self.actualList)#比較后將結(jié)果存放到輸出數(shù)組中 result=module.outPut(self.expectedList)#tmp=unicode(self.__data, 'utf-8')#return str(self.actualListreturn result_typeDict["actual"] = "String"def actual(self):#調(diào)用module函數(shù)比較測(cè)試結(jié)果result=module.outPut(self.actualList) return result
測(cè)試結(jié)果: