├── LiveApp.py ├── README.md ├── TestCase └── TestCase.xlsx ├── TestData └── caseData.txt └── media ├── testImage.png └── testUpload.mp3 /LiveApp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | 4 | # Todo:接口自动化测试 5 | # Author:归根落叶 6 | # Blog:http://this.ispenn.com 7 | 8 | import json 9 | import http.client,mimetypes 10 | from urllib.parse import urlencode 11 | import random 12 | import time 13 | import re 14 | import logging 15 | import os,sys 16 | try: 17 | import xlrd 18 | except: 19 | os.system('pip install -U xlrd') 20 | import xlrd 21 | try: 22 | from pyDes import * 23 | except ImportError as e: 24 | os.system('pip install -U pyDes --allow-external pyDes --allow-unverified pyDes') 25 | from pyDes import * 26 | import hashlib 27 | import base64 28 | import smtplib 29 | from email.mime.text import MIMEText 30 | 31 | log_file = os.path.join(os.getcwd(),'log/liveappapi.log') 32 | log_format = '[%(asctime)s] [%(levelname)s] %(message)s' 33 | logging.basicConfig(format=log_format,filename=log_file,filemode='w',level=logging.DEBUG) 34 | console = logging.StreamHandler() 35 | console.setLevel(logging.DEBUG) 36 | formatter = logging.Formatter(log_format) 37 | console.setFormatter(formatter) 38 | logging.getLogger('').addHandler(console) 39 | 40 | #获取并执行测试用例 41 | def runTest(testCaseFile): 42 | testCaseFile = os.path.join(os.getcwd(),testCaseFile) 43 | if not os.path.exists(testCaseFile): 44 | logging.error('测试用例文件不存在!!!') 45 | sys.exit() 46 | testCase = xlrd.open_workbook(testCaseFile) 47 | table = testCase.sheet_by_index(0) 48 | errorCase = [] 49 | correlationDict = {} 50 | correlationDict['${hashPassword}'] = hash1Encode('123456') 51 | correlationDict['${session}'] = None 52 | for i in range(1,table.nrows): 53 | correlationDict['${randomEmail}'] = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz',6)) + '@automation.test' 54 | correlationDict['${randomTel}'] = '186' + str(random.randint(10000000,99999999)) 55 | correlationDict['${timestamp}'] = int(time.time()) 56 | if table.cell(i,10).value.replace('\n','').replace('\r','') != 'Yes': 57 | continue 58 | num = str(int(table.cell(i,0).value)).replace('\n','').replace('\r','') 59 | api_purpose = table.cell(i,1).value.replace('\n','').replace('\r','') 60 | api_host = table.cell(i,2).value.replace('\n','').replace('\r','') 61 | request_url = table.cell(i,3).value.replace('\n','').replace('\r','') 62 | request_method = table.cell(i,4).value.replace('\n','').replace('\r','') 63 | request_data_type = table.cell(i,5).value.replace('\n','').replace('\r','') 64 | request_data = table.cell(i,6).value.replace('\n','').replace('\r','') 65 | encryption = table.cell(i,7).value.replace('\n','').replace('\r','') 66 | check_point = table.cell(i,8).value 67 | correlation = table.cell(i,9).value.replace('\n','').replace('\r','').split(';') 68 | for key in correlationDict: 69 | if request_url.find(key) > 0: 70 | request_url = request_url.replace(key,str(correlationDict[key])) 71 | if request_data_type == 'Form': 72 | dataFile = request_data 73 | if os.path.exists(dataFile): 74 | fopen = open(dataFile,encoding='utf-8') 75 | request_data = fopen.readline() 76 | fopen.close() 77 | for keyword in correlationDict: 78 | if request_data.find(keyword) > 0: 79 | request_data = request_data.replace(keyword,str(correlationDict[keyword])) 80 | try: 81 | if encryption == 'MD5': 82 | request_data = json.loads(request_data) 83 | status,md5 = getMD5(api_host,urlencode(request_data).replace("%27","%22")) 84 | if status != 200: 85 | logging.error(num + ' ' + api_purpose + "[ " + str(status) + " ], 获取md5验证码失败!!!") 86 | continue 87 | request_data = dict(request_data,**{"sign":md5.decode("utf-8")}) 88 | request_data = urlencode(request_data).replace("%27","%22") 89 | elif encryption == 'DES': 90 | request_data = json.loads(request_data) 91 | request_data = urlencode({'param':encodePostStr(request_data)}) 92 | else: 93 | request_data = urlencode(json.loads(request_data)) 94 | except Exception as e: 95 | logging.error(num + ' ' + api_purpose + ' 请求的数据有误,请检查[Request Data]字段是否是标准的json格式字符串!') 96 | continue 97 | elif request_data_type == 'Data': 98 | dataFile = request_data 99 | if os.path.exists(dataFile): 100 | fopen = open(dataFile,encoding='utf-8') 101 | request_data = fopen.readline() 102 | fopen.close() 103 | for keyword in correlationDict: 104 | if request_data.find(keyword) > 0: 105 | request_data = request_data.replace(keyword,str(correlationDict[keyword])) 106 | request_data = request_data.encode('utf-8') 107 | elif request_data_type == 'File': 108 | dataFile = request_data 109 | if not os.path.exists(dataFile): 110 | logging.error(num + ' ' + api_purpose + ' 文件路径配置无效,请检查[Request Data]字段配置的文件路径是否存在!!!') 111 | continue 112 | fopen = open(dataFile,'rb') 113 | data = fopen.read() 114 | fopen.close() 115 | request_data = ''' 116 | ------WebKitFormBoundaryDf9uRfwb8uzv1eNe 117 | Content-Disposition:form-data;name="file";filename="%s" 118 | Content-Type: 119 | Content-Transfer-Encoding:binary 120 | 121 | %s 122 | ------WebKitFormBoundaryDf9uRfwb8uzv1eNe-- 123 | ''' % (os.path.basename(dataFile),data) 124 | status,resp = interfaceTest(num,api_purpose,api_host,request_url,request_data,check_point,request_method,request_data_type,correlationDict['${session}']) 125 | if status != 200: 126 | errorCase.append((num + ' ' + api_purpose,str(status),'http://'+api_host+request_url,resp)) 127 | continue 128 | for j in range(len(correlation)): 129 | param = correlation[j].split('=') 130 | if len(param) == 2: 131 | if param[1] == '' or not re.search(r'^\[',param[1]) or not re.search(r'\]$',param[1]): 132 | logging.error(num + ' ' + api_purpose + ' 关联参数设置有误,请检查[Correlation]字段参数格式是否正确!!!') 133 | continue 134 | value = resp 135 | for key in param[1][1:-1].split(']['): 136 | try: 137 | temp = value[int(key)] 138 | except: 139 | try: 140 | temp = value[key] 141 | except: 142 | break 143 | value = temp 144 | correlationDict[param[0]] = value 145 | return errorCase 146 | 147 | # 接口测试 148 | def interfaceTest(num,api_purpose,api_host,request_url,request_data,check_point,request_method,request_data_type,session): 149 | headers = {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8', 150 | 'X-Requested-With':'XMLHttpRequest', 151 | 'Connection':'keep-alive', 152 | 'Referer':'http://' + api_host, 153 | 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36'} 154 | if session is not None: 155 | headers['Cookie'] = 'session=' + session 156 | if request_data_type == 'File': 157 | headers['Content-Type'] = 'multipart/form-data;boundary=----WebKitFormBoundaryDf9uRfwb8uzv1eNe;charset=UTF-8' 158 | elif request_data_type == 'Data': 159 | headers['Content-Type'] = 'text/plain; charset=UTF-8' 160 | 161 | conn = http.client.HTTPConnection(api_host) 162 | if request_method == 'POST': 163 | conn.request('POST',request_url,request_data,headers=headers) 164 | elif request_method == 'GET': 165 | conn.request('GET',request_url+'?'+request_data,headers=headers) 166 | else: 167 | logging.error(num + ' ' + api_purpose + ' HTTP请求方法错误,请确认[Request Method]字段是否正确!!!') 168 | return 400,request_method 169 | response = conn.getresponse() 170 | status = response.status 171 | resp = response.read() 172 | if status == 200: 173 | resp = resp.decode('utf-8') 174 | if re.search(check_point,str(resp)): 175 | logging.info(num + ' ' + api_purpose + ' 成功, ' + str(status) + ', ' + str(resp)) 176 | return status,json.loads(resp) 177 | else: 178 | logging.error(num + ' ' + api_purpose + ' 失败!!!, [ ' + str(status) + ' ], ' + str(resp)) 179 | return 2001,resp 180 | else: 181 | logging.error(num + ' ' + api_purpose + ' 失败!!!, [ ' + str(status) + ' ], ' + str(resp)) 182 | return status,resp.decode('utf-8') 183 | 184 | #获取md5验证码 185 | def getMD5(url,postData): 186 | headers = {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8', 187 | 'X-Requested-With':'XMLHttpRequest'} 188 | conn = http.client.HTTPConnection('this.ismyhost.com') 189 | conn.request('POST','/get_isignature',postData,headers=headers) 190 | response = conn.getresponse() 191 | return response.status,response.read() 192 | 193 | # hash1加密 194 | def hash1Encode(codeStr): 195 | hashobj = hashlib.sha1() 196 | hashobj.update(codeStr.encode('utf-8')) 197 | return hashobj.hexdigest() 198 | 199 | # DES加密 200 | def desEncode(desStr): 201 | k = des('secretKEY', padmode=PAD_PKCS5) 202 | encodeStr = base64.b64encode(k.encrypt(json.dumps(desStr))) 203 | return encodeStr 204 | 205 | # 字典排序 206 | def encodePostStr(postData): 207 | keyDict = {'key':'secretKEY'} 208 | mergeDict = dict(postData, **keyDict) 209 | mergeDict = sorted(mergeDict.items()) 210 | postStr = '' 211 | for i in mergeDict: 212 | postStr = postStr + i[0] + '=' + i[1] + '&' 213 | postStr = postStr[:-1] 214 | hashobj = hashlib.sha1() 215 | hashobj.update(postStr.encode('utf-8')) 216 | token = hashobj.hexdigest() 217 | postData['token'] = token 218 | return desEncode(postData) 219 | 220 | #发送通知邮件 221 | def sendMail(text): 222 | sender = 'no-reply@myhost.cn' 223 | receiver = ['penn@myhost.cn'] 224 | mailToCc = ['penn@myhost.cn'] 225 | subject = '[AutomantionTest]接口自动化测试报告通知' 226 | smtpserver = 'smtp.exmail.qq.com' 227 | username = 'no-reply@myhost.cn' 228 | password = 'password' 229 | 230 | msg = MIMEText(text,'html','utf-8') 231 | msg['Subject'] = subject 232 | msg['From'] = sender 233 | msg['To'] = ';'.join(receiver) 234 | msg['Cc'] = ';'.join(mailToCc) 235 | smtp = smtplib.SMTP() 236 | smtp.connect(smtpserver) 237 | smtp.login(username, password) 238 | smtp.sendmail(sender, receiver + mailToCc, msg.as_string()) 239 | smtp.quit() 240 | 241 | def main(): 242 | errorTest = runTest('TestCase/TestCasePre.xlsx') 243 | if len(errorTest) > 0: 244 | html = '
接口自动化定期扫描,共有 ' + str(len(errorTest)) + ' 个异常接口,列表如下:' + '接口 | 状态 | 接口地址 | 接口返回值 |
---|---|---|---|
' + test[0] + ' | ' + test[1] + ' | ' + test[2] + ' | ' + test[3] + ' |