目錄
Python接口測(cè)試課程(第一天)-Python基礎(chǔ)
Python接口測(cè)試課程(第二天)-接口測(cè)試快速實(shí)踐
Python接口測(cè)試課程(第三天)-接口安全驗(yàn)證,參數(shù)化及斷言
Python接口測(cè)試課程(第四天)-接口測(cè)試框架實(shí)現(xiàn)
更多學(xué)習(xí)資料請(qǐng)加添加作者微信:lockingfree獲取
第三天: Python接口測(cè)試(二)
各種類型接口的測(cè)試
GET請(qǐng)求接口
requests.get(url=url, params=params)
表單類型
requests.post(url=url, data=data)
REST類型
requests.post(url=url, headers={"Content-Type": "application/json"}, data=json.dumps(data)
上傳文件
requests.post(url=url, files={"file": open("1.jpg", "rb")})
Session依賴
session=requests.session(); session.post(...); session.post()
接口依賴
- 接口依賴之動(dòng)態(tài)中間值
resp=requests.get(...);token=resp.split("=")[1];resp2=requests.post(....token...)
驗(yàn)簽接口
import hashlib
def md5(str):
m = hashlib.md5()
m.update(str.encode('utf8'))
return m.hexdigest() #返回摘要治泥,作為十六進(jìn)制數(shù)據(jù)字符串值
def makeSign(params):
if not isinstance(params, dict):
print("參數(shù)格式不正確拌倍,必須為字典格式")
return None
if 'sign' in params:
params.pop('sign')
sign = ''
for key in sorted(params.keys()):
sign = sign + key + '=' + str(params[key]) + '&'
sign = md5(sign + 'appsecret=' + appsecret)
params['sign'] = sign
return params
data = makeSign(data);resp = requests.post(url=url, headers=headers, data=json.dumps(data))
- 接口依賴之Mock Server
Mock和單元測(cè)試的樁(Stub)類似, 是通過建立一個(gè)模擬對(duì)象來解決依賴問題的一種方法.
應(yīng)用場(chǎng)景:
1. 依賴第三方系統(tǒng)接口, 無法調(diào)試
2. 所依賴接口尚未具備(前后端分離項(xiàng)目, 前端開發(fā)時(shí),后端接口尚未開發(fā)完畢)
3. 所依賴接口需要修改或不穩(wěn)定
4. 依賴接口較多或場(chǎng)景復(fù)雜, 所依賴接口不是主要驗(yàn)證目標(biāo)的
解決方法:
1. 通過Mock.js/RAP/RAP2來動(dòng)態(tài)生成, 模擬接口返回?cái)?shù)據(jù)
2. 自己使用Flask大家簡(jiǎn)單的Mock接口
3. 使用Python自帶的mock庫(kù)
...
SOAP接口
pip install suds
from suds.client import Client
ip = '127.0.0.1'
port = '5001'
client = Client("http://%s:%s/?wsdl" % (ip, port))
result = client.service.addUser("張790", "123456")
print(result)
XML-RPC接口
import xmlrpc.client
user = xmlrpc.client.ServerProxy('http://127.0.0.1:5002')
print(user.getAll())
參數(shù)化
參數(shù)化是用來解決動(dòng)態(tài)參數(shù)問題的
數(shù)據(jù)文件參數(shù)化
- csv數(shù)據(jù)文件
- 優(yōu)點(diǎn):以逗號(hào)分隔束世,輕量級(jí)
- 缺點(diǎn):參數(shù)過多不便于區(qū)分
import csv
csv_data = csv.reader(open('data/reg.csv'))
- config數(shù)據(jù)文件
- 優(yōu)點(diǎn):方便支持多組數(shù)據(jù)悴势,支持備注
import configparser
cf=configparser.ConfigParser()
cf.read('data/reg.config', encoding='utf-8')
cf.sections()
cf.options(section)
cf.get(section,option)
- json數(shù)據(jù)文件
- 優(yōu)點(diǎn):REST接口常用數(shù)據(jù)格式消略,格式清楚,適用于多參數(shù)及含有嵌套參數(shù)
- 缺點(diǎn):不支持備注,多組數(shù)據(jù)不清晰
import json
with open('data/reg.json', encoding='utf-8') as f:
json_data = json.loads(f) #json_data為列表或字典
json的序列化和反序列化
需求:python的字典/列表等數(shù)據(jù)格式為內(nèi)存對(duì)象,需要做存儲(chǔ)(持久化)和進(jìn)行數(shù)據(jù)交換
序列化: 從內(nèi)存對(duì)象到可存儲(chǔ)數(shù)據(jù), 方便存儲(chǔ)和交換數(shù)據(jù)
json.dumps: 列表/字典/json->字符串 ```str_data = json.dumps(dict_data)```
json.dump: 列表/字典/json->數(shù)據(jù)文件 ```json.dump(dict_data, open(data_file, "w"))```
反序列化: 從存儲(chǔ)數(shù)據(jù)到內(nèi)存對(duì)象
json.loads: 字符串->字典/列表```json.loads('{"a":1,"b":2}') #得到字典{"a":1,"b":2}```
json.load: json數(shù)據(jù)文檔->列表/字典```dict_data = json.load(open('data.json'))```
- excel數(shù)據(jù)文件
- 優(yōu)點(diǎn):直觀贤旷,構(gòu)造數(shù)據(jù)方便
- 缺點(diǎn):嵌套數(shù)據(jù)不方便格式化
pip install xlrd
import xlrd
wb=xlrd.open_workbook("data/reg.xlsx")
sh=wb.sheet_by_index(0)
sh=wb.sheet_by_name('Sheet1")
sh.nrows
sh.ncols
sh.cell(x,y).value
- xml數(shù)據(jù)文件
- 優(yōu)點(diǎn):方便自定義多層結(jié)構(gòu),SOAP,RPC通用格式
from xml.dom.minidom import parse
dom=parse('data/reg.xml')
root=dom.documentElement
user_nodes=root.getElementsByTagName("user")
user_node.getAttribute('title')
user_node.hasAttribute('title')
name_node=user_node.getElementsByTagName('name')[0]
name=name_node.childNodes[0].data
案例1: 自動(dòng)執(zhí)行excel用例并將結(jié)果回寫excel
數(shù)據(jù)文件: test_user.xlsx
TestCase | Url | Method | DataType | Data | Excepted | Resp.text | Status |
---|---|---|---|---|---|---|---|
test_user_reg_normal | /api/user/reg/ | POST | JSON | {"name":"九小1","passwd": "123456"} | resp.json()["code"]=="100000" | ||
test_user_login_normal | /api/user/login/ | POST | FORM | {"name":"九小1","passwd": "123456"} | "成功" in resp.text |
import xlrd
from xlutils.copy import copy
import json
import requests
import sys
base_url = "http://127.0.0.1:5000"
def run_excel(file_name, save_file="result.xls"):
wb=xlrd.open_workbook(file_name)
sh=wb.sheet_by_index(0)
wb2 = copy(wb)
sh2 = wb2.get_sheet(0)
for i in range(1,sh.nrows):
url = base_url + sh.cell(i,1).value
data = json.loads(sh.cell(i,4).value)
headers = {}
method = sh.cell(i,2).value
data_type = sh.cell(i,3).value
excepted = sh.cell(i,5).value
if data_type.lower() == 'json':
data = json.dumps(data)
headers = {"Content-Type":"application/json"}
if method.lower() == "get":
resp = requests.get(url=url,headers=headers)
else:
resp = requests.post(url=url,headers=headers,data=data)
if eval(excepted):
status = "PASS"
else:
status = "FAIL"
sh2.write(i,6, resp.text)
sh2.write(i,7, status)
wb2.save(save_file)
print("保存成功")
if __name__ == "__main__":
if len(sys.argv)==2:
run_excel(sys.argv[1])
elif len(sys.argv)>2:
run_excel(sys.argv[1],sys.argv[2])
else:
print("調(diào)用格式: python run_excel.py 用例文件 輸出文件")
保存腳本為run_excel.py, (最好和數(shù)據(jù)文件test_user.xlsx放在同一目錄下), 在腳本所在目錄打開命令行,運(yùn)行
python run_excel.py test_user.xlsx
生成的result.xls預(yù)覽
TestCase | Url | Method | DataType | Data | Excepted | Resp.text | Status |
---|---|---|---|---|---|---|---|
test_user_reg_normal | /api/user/reg/ | POST | JSON | {"name":"九小1","passwd": "123456"} | resp.json()["code"]=="100000" | {"code":"100001","data":{"name":"\u4e5d\u5c0f1","passwod":"e10adc3949ba59abbe56e057f20f883e"},"msg":"\u5931\u8d25\uff0c\u7528\u6237\u5df2\u5b58\u5728"} | FAIL |
test_user_login_normal | /api/user/login/ | POST | FORM | {"name":"九小1","passwd": "123456"} | "成功" in resp.text | <h1>登錄成功</h1> |
PASS |
隨機(jī)數(shù)據(jù)參數(shù)化
import random
- 隨機(jī)數(shù)
- random.random(): 隨機(jī)0砾脑,1
- random.randint(0,100): 隨機(jī)整數(shù)
- random.randrange(0,100,2): 隨機(jī)偶數(shù)
- random.uniform(1,100): 隨機(jī)浮點(diǎn)數(shù)
- 隨機(jī)選擇
- random.choice('abcdefg')
- random.choice(['趙','錢','孫','李','周'])
隨機(jī)姓名的實(shí)現(xiàn):
#隨機(jī)漢字: chr(random.randint(0x4e00, 0x9fbf))
name=random.choice(['趙','錢','孫','李','周'])+chr(random.randint(0x4e00, 0x9fbf)
- 隨機(jī)樣本
- random.sample('abcdefg', 2)
隨機(jī)2個(gè)字符拼接: ''.join(random.sample('abcdefg',2)
- 洗牌
- random.shuffle([1, 2, 3, 4, 5, 6]): 隨機(jī)改版列表數(shù)據(jù)
斷言/檢查點(diǎn)
斷言/檢查點(diǎn)是用來自動(dòng)驗(yàn)證接口返回?cái)?shù)據(jù)/業(yè)務(wù)操作的結(jié)果是否滿足預(yù)期
響應(yīng)斷言
正則表達(dá)式
- 元字符
- . : 任意字符
- \d: 任意數(shù)字 - \D: 任意非數(shù)字
- \w: 任意字符或數(shù)字 - \W: 任意非字符及數(shù)字
- \s: 任意空白字符(包含空格或\n等) - \S: 任意非空白字符
- ^: 匹配開頭
- $: 匹配結(jié)尾
- {n,m}: 匹配n-m個(gè)重復(fù)
- : 匹配重復(fù)任意次(包含0次)
- : 匹配重復(fù)至少一次
- ? : 匹配0或1次
- (): 分組,獲取需要的部分?jǐn)?shù)據(jù)
- | : 或, 匹配多個(gè)pattern
- \元字符: 取消元字符轉(zhuǎn)義
- 貪婪匹配及非貪婪匹配
- 系統(tǒng)函數(shù)
- re.findall(): re.S,支持跨行
- re.match()
- re.search()
- re.sub()/re.subn()
- re.complie()
數(shù)據(jù)庫(kù)斷言
從數(shù)據(jù)庫(kù)讀取數(shù)據(jù),驗(yàn)證數(shù)據(jù)庫(kù)數(shù)據(jù)是否符合預(yù)期
- MySQL斷言
pip install pymysql
- 導(dǎo)入pymysql:
import pymysql
- 建立數(shù)據(jù)庫(kù)連接:
conn = pymysql.connect(host='',port=3306,db='',user='',passwd='',charset='utf8')
- 從連接建立操作游標(biāo):
cur=conn.cursor()
- 使用游標(biāo)執(zhí)行sql命令:
cur.execute("select * from user")
- 獲取執(zhí)行結(jié)果:
- cur.fetchall(): 獲取所有結(jié)果
- cur.fetchmany(3): 獲取多條結(jié)果
- cur.fetchone(): 獲取一條結(jié)果
- 關(guān)閉游標(biāo)及連接(先關(guān)游標(biāo)再關(guān)連接):
cur.close();conn.close()
- PostgreSQL
pip install pyscopg2
import pyscopg2
conn=pyscopg2.connect(host='',port='',dbname='',user='',password='') # 注意是dbname, password
cur=conn.curser()
cur.execute("...")
cur.fetchall()
- Oracle
pip install cx_Oracle
...
- Mongodb
pip install pymongo
from pymongo import MongoClient
conn = MongoClient('', 27017)
db = conn.mydb
my_set = db.test_set
for i in my_set.find({"name": "張三"}):
print(i)
print(my_set.findone({"name": "張三"}))
- Redis斷言
pip install redis
import redis
r = redis.Redis(host='192.168.100.198', port=6379, password='!QE%^E2sdf23RGF@ml239', db=0)
print(r.dbsize())
print(r.get('package'))
print(r.hget('event_order_advance_008aea6a62115ec4923829ee09f76a9c18243f5d', 'user'))
服務(wù)器斷言
pip install paramiko
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.241', 22, username='root', password='1234567', timeout=4)
stdin, stdout, stderr = client.exec_command('cat /proc/meminfo')
print(stdout.read())
client.close()
完整用例:
import requests
import pytest
import json
import hashlib
import re
def md5(string):
m = hashlib.md5()
m.update(string.encode('utf8'))
return m.hexdigest()
def makeSign(data):
sign=''
for key in sorted(data.keys()):
sign += key + '=' + str(data[key]) + '&'
sign += 'appsecret=NTA3ZTU2ZWM5ZmVkYTVmMDBkMDM3YTBi'
data['sign'] = md5(sign)
return data
class DB():
def __init__(self):
# 建立連接
self.conn = pymysql.connect(host='localhost',port=3307,user='root',passwd='',db='api',charset='utf8')
# 建立一個(gè)游標(biāo)
self.cur = self.conn.cursor()
def __del__(self):
self.cur.close()
self.conn.close()
def getUserByName(self, name):
self.cur.execute("select * from user where name='%s'" % name)
return self.cur.fetchone()
def checkUser(self, name, passwd):
user = self.getUserByName(name)
if user:
if user[2] == md5(passwd):
return True
else:
return False
else:
return None
class TestUser(): # pytest識(shí)別不能用__init__方法
base_url = 'http://127.0.0.1:5000'
db = DB()
def test_login(self):
url = self.base_url + '/api/user/login/'
data = {"name": "張三", "passwd": "123456"}
resp = requests.post(url=url, data=data)
#斷言
assert resp.status_code == 200
assert '登錄成功' in resp.text
def test_reg(self):
url = self.base_url + '/api/user/reg/'
headers = {"Content-Type": "application/json"}
data = {'name': '張10', 'passwd': '123456'}
resp = requests.post(url=url, headers=headers, data=json.dumps(data))
#斷言
assert resp.json()['code'] == '100000'
assert resp.json()['msg'] == '成功'
assert self.db.getUserByName('張10')
def test_uploadImage(self):
url = self.base_url + '/api/user/uploadImage/'
files = {'file': open("復(fù)習(xí).txt")}
resp = requests.post(url=url, files=files)
#斷言
assert resp.status_code == 200
assert '成功' in resp.text
# todo 服務(wù)器斷言
def test_getUserList(self):
session = requests.session()
login_url = self.base_url + '/api/user/login/'
login_data = {"name": "張三", "passwd": "123456"}
session.post(url=login_url, data=login_data)
url = self.base_url + '/api/user/getUserList/'
resp = session.get(url=url)
#斷言
assert resp.status_code == 200
assert '用戶列表' in resp.text
assert re.findall('\w{32}',t2) != []
def test_updateUser(self):
session = requests.session() # 接口依賴的接口需要用session
get_token_url = self.base_url + '/api/user/getToken/'
params = {"appid": '136425'}
token_resp = session.get(url=get_token_url, params=params)
assert re.findall('token=\w{32}$')
token = token_resp.text.split('=')[1]
url = self.base_url + '/api/user/updateUser/?token=' + token
data = {'name': '張三', 'passwd': '234567'}
headers = {"Content-Type": "application/json"}
resp = session.post(url=url, headers=headers, data=json.dumps(data))
#斷言
assert resp.status_code == 200
assert resp.json()['code'] == '100000'
assert resp.json()['msg'] == '成功'
assert self.db.checkUser('張三', '234567')
def test_delUser(self):
url = self.base_url + '/api/user/delUser/'
headers = {"Content-Type": "application/json"}
data = {'name': '張10', 'passwd': '123456'}
data = makeSign(data)
resp = requests.post(url=url, headers=headers, data=json.dumps(data))
#斷言
assert resp.status_code == 200
assert resp.json()['code'] == '100000'
assert resp.json()['msg'] == '成功'
assert not self.db.getUserByName('張10')
if __name__ == '__main__':
t = TestUser()
# t.test_updateUser()
# t.test_updateUser()
t.test_delUser()
# pytest.main("-q test_user2.py")