python自动化测试脚本后端_基于 python 的接口自动化测试
本文來(lái)自作者:孫彥輝?在 GitChat 上精彩分享,「閱讀原文」看看大家和作者交流了哪些問(wèn)題
一、簡(jiǎn)介
本文從一個(gè)簡(jiǎn)單的登錄接口測(cè)試入手,一步步調(diào)整優(yōu)化接口調(diào)用姿勢(shì);
然后簡(jiǎn)單討論了一下接口測(cè)試框架的要點(diǎn);
最后介紹了一下我們目前正在使用的接口測(cè)試框架?pithy。
期望讀者可以通過(guò)本文對(duì)接口自動(dòng)化測(cè)試有一個(gè)大致的了解。
二、引言
為什么要做接口自動(dòng)化測(cè)試?
在當(dāng)前互聯(lián)網(wǎng)產(chǎn)品迭代頻繁的背景下,回歸測(cè)試的時(shí)間越來(lái)越少,很難在每個(gè)迭代都對(duì)所有功能做完整回歸。
但接口自動(dòng)化測(cè)試因其實(shí)現(xiàn)簡(jiǎn)單、維護(hù)成本低,容易提高覆蓋率等特點(diǎn),越來(lái)越受重視。
為什么要自己寫(xiě)框架呢?
使用 requets + unittest 很容易實(shí)現(xiàn)接口自動(dòng)化測(cè)試,而且 requests 的api已經(jīng)非常人性化,非常簡(jiǎn)單。
但通過(guò)封裝以后(特別是針對(duì)公司內(nèi)特定接口),再加上對(duì)一些常用工具的封裝,可以進(jìn)一步提高業(yè)務(wù)腳本編寫(xiě)效率。
三、環(huán)境準(zhǔn)備
確保本機(jī)已安裝 python2.7 以上版本,然后安裝如下庫(kù):pip install flask
pip install requests
后面我們會(huì)使用 flask 寫(xiě)一個(gè)用來(lái)測(cè)試的接口,使用requests去測(cè)試。
四、測(cè)試接口準(zhǔn)備
下面使用 flask 實(shí)現(xiàn)兩個(gè) http 接口,一個(gè)登錄,另外一個(gè)查詢(xún)?cè)斍?#xff0c;但需要登錄后才可以,新建一個(gè) demo.py 文件(注意,不要使用windows記事本),把下面代碼 copy 進(jìn)去,然后保存、關(guān)閉。
接口代碼#!/usr/bin/python# coding=utf-8from flask import Flask, request, session, jsonify
USERNAME = 'admin'PASSWORD = '123456'app = Flask(__name__)
app.secret_key = 'pithy'@app.route('/login', methods=['GET', 'POST'])def login():
error = None
if request.method == 'POST': ? ? ? ?if request.form['username'] != USERNAME:
error = 'Invalid username'
elif request.form['password'] != PASSWORD:
error = 'Invalid password'
else:
session['logged_in'] = True
return jsonify({'code': 200, 'msg': 'success'}) ? ?return jsonify({'code': 401, 'msg': error}), 401@app.route('/info', methods=['get'])def info():
if not session.get('logged_in'): ? ? ? ?return jsonify({'code': 401, 'msg': 'please login !!'}) ? ?return jsonify({'code': 200, 'msg': 'success', 'data': 'info'})if __name__ == '__main__':
app.run(debug=True)
最后執(zhí)行如下命令:python demo.py
響應(yīng)如下:* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
大家可以看到服務(wù)已經(jīng)起來(lái)了。
接口信息
登錄接口
請(qǐng)求url
/login
請(qǐng)求方法
post
請(qǐng)求參數(shù)
| 參數(shù)名稱(chēng) ? ? ? ?| 參數(shù)類(lèi)型 ? | ?參數(shù)說(shuō)明 ?|
| :————: ? | :——-: ?| :——: ?|
| username ? ? | String | ? 登錄名稱(chēng) ? ? |
| password ? ? ? ?| ? String
| ? 登錄密碼 ? |
響應(yīng)信息
| 參數(shù)名稱(chēng) ? ? ? ?| 參數(shù)類(lèi)型 ? | ?參數(shù)說(shuō)明 ?|
| :————: ? | :——-: ?| :——: ?|
| code ? ? ? ?| ? Integer ? | ? 結(jié)果code ? |
| msg ? ? ? ?|
String ? | ? 結(jié)果信息 ? |
詳情接口
請(qǐng)求url
/info
請(qǐng)求方法
get
請(qǐng)求 cookies
| 參數(shù)名稱(chēng) ? ? ? ?| 參數(shù)類(lèi)型 ? | ?參數(shù)說(shuō)明 ?|
| :————: ? | :——-: ?| :——: ?|
| session ? ? | String | ? session ? ? |
響應(yīng)信息
| 參數(shù)名稱(chēng) ? ? ? ?| 參數(shù)類(lèi)型 ? | ?參數(shù)說(shuō)明 ?|
| :————: ? | :——-: ?| :——: ?|
| code ? ? ? ?| ? Integer ? | ? 結(jié)果code ? |
| msg ? ? ? ?|
String ? | ? 結(jié)果信息 ? |
| data ? ? ? ?| ? String ? | ? 數(shù)據(jù)信息 ? |
五、編寫(xiě)接口測(cè)試
測(cè)試思路
使用 requests [ http://docs.python-requests.org/zh_CN/latest/user/quickstart.html ] 庫(kù)模擬發(fā)送 HTTP 請(qǐng)求。
使用 python 標(biāo)準(zhǔn)庫(kù)里 unittest 寫(xiě)測(cè)試 case。
腳本實(shí)現(xiàn)#!/usr/bin/python# coding=utf-8import requestsimport unittestclass TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.login_url = 'http://127.0.0.1:5000/login'
cls.info_url = 'http://127.0.0.1:5000/info'
cls.username = 'admin'
cls.password = '123456'
def test_login(self):
"""
測(cè)試登錄
"""
data = { ? ? ? ? ? ?'username': self.username, ? ? ? ? ? ?'password': self.password
}
response = requests.post(self.login_url, data=data).json() ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
測(cè)試info接口
"""
data = { ? ? ? ? ? ?'username': self.username, ? ? ? ? ? ?'password': self.password
}
response_cookies = requests.post(self.login_url, data=data).cookies
session = response_cookies.get('session') ? ? ? ?assert session
info_cookies = { ? ? ? ? ? ?'session': session
}
response = requests.get(self.info_url, cookies=info_cookies).json() ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'
六、優(yōu)化
封裝接口調(diào)用
寫(xiě)完這個(gè)測(cè)試登錄腳本,你或許會(huì)發(fā)現(xiàn),在整個(gè)項(xiàng)目的測(cè)試過(guò)程,登錄可能不止用到一次,如果每次都這么寫(xiě),會(huì)不會(huì)太冗余了?
對(duì),確實(shí)太冗余了,下面做一下簡(jiǎn)單的封裝,把登錄接口的調(diào)用封裝到一個(gè)方法里,把調(diào)用參數(shù)暴漏出來(lái),示例腳本如下:#!/usr/bin/python# coding=utf-8import requestsimport unittesttry: ? ?from urlparse import urljoinexcept ImportError: ? ?from urllib.parse import urljoinclass DemoApi(object):
def __init__(self, base_url):
self.base_url = base_url ? ?def login(self, username, password):
"""
登錄接口
:param username: 用戶(hù)名
:param password: 密碼
"""
url = urljoin(self.base_url, 'login')
data = { ? ? ? ? ? ?'username': username, ? ? ? ? ? ?'password': password
} ? ? ? ?return requests.post(url, data=data).json() ? ?def get_cookies(self, username, password):
"""
獲取登錄cookies
"""
url = urljoin(self.base_url, 'login')
data = { ? ? ? ? ? ?'username': username, ? ? ? ? ? ?'password': password
} ? ? ? ?return requests.post(url, data=data).cookies ? ?def info(self, cookies):
"""
詳情接口
"""
url = urljoin(self.base_url, 'info') ? ? ? ?return requests.get(url, cookies=cookies).json()class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.base_url = 'http://127.0.0.1:5000'
cls.username = 'admin'
cls.password = '123456'
cls.app = DemoApi(cls.base_url) ? ?def test_login(self):
"""
測(cè)試登錄
"""
response = self.app.login(self.username, self.password) ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
測(cè)試獲取詳情信息
"""
cookies = self.app.get_cookies(self.username, self.password)
response = self.app.info(cookies) ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'
OK,在這一個(gè)版本中,我們不但在把登錄接口的調(diào)用封裝成了一個(gè)實(shí)例方法,實(shí)現(xiàn)了復(fù)用,而且還把 host(self.base_url)提取了出來(lái)。
但問(wèn)題又來(lái)了,登錄之后,登錄接口的 http 響應(yīng)會(huì)把?session以 cookie 的形式 set 到客戶(hù)端,之后的接口都會(huì)使用此 session 去請(qǐng)求。
還有,就是在接口調(diào)用過(guò)程中,希望可以把日志打印出來(lái),以便調(diào)試或者出錯(cuò)時(shí)查看。
好吧,我們?cè)賮?lái)改一版。
保持 cookies &增加 log 信息
使用 requests 庫(kù)里的同一個(gè) Session 對(duì)象 (它也會(huì)在同一個(gè) Session 實(shí)例發(fā)出的所有請(qǐng)求之間保持 cookie ),即可解決上面的問(wèn)題,示例代碼如下:#!/usr/bin/python# coding=utf-8import unittestfrom pprint import pprintfrom requests.sessions import Sessiontry: ? ?from urlparse import urljoinexcept ImportError: ? ?from urllib.parse import urljoinclass DemoApi(object):
def __init__(self, base_url):
self.base_url = base_url ? ? ? ?# 創(chuàng)建session實(shí)例
self.session = Session() ? ?def login(self, username, password):
"""
登錄接口
:param username: 用戶(hù)名
:param password: 密碼
"""
url = urljoin(self.base_url, 'login')
data = { ? ? ? ? ? ?'username': username, ? ? ? ? ? ?'password': password
}
response = self.session.post(url, data=data).json()
print('\n*****************************************')
print(u'\n1、請(qǐng)求url: \n%s' % url)
print(u'\n2、請(qǐng)求頭信息:')
pprint(self.session.headers)
print(u'\n3、請(qǐng)求參數(shù):')
pprint(data)
print(u'\n4、響應(yīng):')
pprint(response) ? ? ? ?return response ? ?def info(self):
"""
詳情接口
"""
url = urljoin(self.base_url, 'info')
response = self.session.get(url).json()
print('\n*****************************************')
print(u'\n1、請(qǐng)求url: \n%s' % url)
print(u'\n2、請(qǐng)求頭信息:')
pprint(self.session.headers)
print(u'\n3、請(qǐng)求cookies:')
pprint(dict(self.session.cookies))
print(u'\n4、響應(yīng):')
pprint(response) ? ? ? ?return responseclass TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.base_url = 'http://127.0.0.1:5000'
cls.username = 'admin'
cls.password = '123456'
cls.app = DemoApi(cls.base_url) ? ?def test_login(self):
"""
測(cè)試登錄
"""
response = self.app.login(self.username, self.password) ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
測(cè)試獲取詳情信息
"""
self.app.login(self.username, self.password)
response = self.app.info() ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'
大功告成,我們把多個(gè)相關(guān)接口調(diào)用封裝到一個(gè)類(lèi)中,使用同一個(gè) requests Session 實(shí)例來(lái)保持 cookies,并且在調(diào)用過(guò)程中打印出了日志,我們所有目標(biāo)都實(shí)現(xiàn)了。
但再看下腳本,又會(huì)感覺(jué)不太舒服,在每個(gè)方法里,都要寫(xiě)一遍print 1、2、3… 要拼url、還要很多細(xì)節(jié)等等。
但其實(shí)我們?真正需要做的只是拼出關(guān)鍵的參數(shù)(url 參數(shù)、body 參數(shù)或者傳入 headers 信息),可不可以只需定義必須的信息,然后把其它共性的東西都封裝起來(lái)呢,統(tǒng)一放到一個(gè)地方去管理?
封裝重復(fù)操作
來(lái),我們?cè)僬硪幌挛覀兊男枨?
首先,不想去重復(fù)做拼接 url 的操作。
然后,不想每次都去手工打印日志。
不想和 requests session 打交道。
只想定義好參數(shù)就直接調(diào)用。
我們先看一下實(shí)現(xiàn)后,腳本可能是什么樣:class DemoApi(object):
def __init__(self, base_url):
self.base_url = base_url ? ?@request(url='login', method='post')
def login(self, username, password):
"""
登錄接口
"""
data = { ? ? ? ? ? ?'username': username, ? ? ? ? ? ?'password': password
} ? ? ? ?return {'data': data} ? ?@request(url='info', method='get')
def info(self):
"""
詳情接口
"""
pass
調(diào)用登錄接口的日志:******************************************************
1、接口描述
登錄接口
2、請(qǐng)求url
http://127.0.0.1:5000/login
3、請(qǐng)求方法
post
4、請(qǐng)求headers
{
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"User-Agent": "python-requests/2.7.0 CPython/2.7.10 Darwin/16.4.0"
}
5、body參數(shù)
{
"password": "123456",
"username": "admin"
}
6、響應(yīng)結(jié)果
{
"code": 200,
"msg": "success"
}
在這里,我們使用 python 的裝飾器功能,把公共特性封裝到裝飾器中去實(shí)現(xiàn)。現(xiàn)在感覺(jué)好多了,沒(méi)什么多余的東西了,我們可以專(zhuān)注于關(guān)鍵參數(shù)的構(gòu)造,剩下的就是如何去實(shí)現(xiàn)這個(gè)裝飾器了,我們先理一下思路:
獲取裝飾器參數(shù)
獲取函數(shù)/方法參數(shù)
把裝飾器和函數(shù)定義的參數(shù)合并
拼接 url
處理 requests session,有則使用,無(wú)則新生成一個(gè)
組裝所有參數(shù),發(fā)送http請(qǐng)求并打印日志
因篇幅限制,源碼不再列出,有興趣的同學(xué)可以查看已經(jīng)實(shí)現(xiàn)的源代碼。
源代碼查看地址:https://github.com/yuyu1987/pithy-test/blob/master/pithy/api.py
七、擴(kuò)展
http 接口請(qǐng)求的姿勢(shì)我們定義好了,我們還可以做些什么呢?
[x] 非HTTP協(xié)議接口
[x] 測(cè)試用例編寫(xiě)
[x] 配置文件管理
[x] 測(cè)試數(shù)據(jù)管理
[x] 工具類(lèi)編寫(xiě)
[x] 測(cè)試報(bào)告生成
[x] 持續(xù)集成
[x] 等等等等
需要做的還是挺多的,要做什么不要做什么,或者先做哪個(gè),我覺(jué)得可以根據(jù)以下幾點(diǎn)去判斷:
是否有利于提高團(tuán)隊(duì)生產(chǎn)效率?
是否有利于提高測(cè)試質(zhì)量?
有沒(méi)有現(xiàn)成的輪子可以用?
下面就幾項(xiàng)主要的點(diǎn)進(jìn)行一下說(shuō)明,限于篇幅,不再展開(kāi)了。
測(cè)試報(bào)告
這個(gè)應(yīng)該是大家最關(guān)心的了,畢竟這是測(cè)試工作的產(chǎn)出;
目前 python 的主流單元測(cè)試框均有 report 插件,因此不建議自己再編寫(xiě),除非有特殊需求的。
pytest:推薦使用?pytest-html? 和?allure pytest。
unittest:推薦使用?HTMLTestRunner。
持續(xù)集成
持續(xù)集成推薦使用?Jenkins,運(yùn)行環(huán)境、定時(shí)任務(wù)、觸發(fā)運(yùn)行、郵件發(fā)送等一系列功能均可以在 Jenkins 上實(shí)現(xiàn)。
測(cè)試用例編寫(xiě)
推薦遵守如下規(guī)則:
原子性:每個(gè)用例保持獨(dú)立,彼此不耦合,以降低干擾。
專(zhuān)一性:一個(gè)用例應(yīng)該專(zhuān)注于驗(yàn)證一件事情,而不是做很多事情,一個(gè)測(cè)試點(diǎn)不要重復(fù)驗(yàn)證。
穩(wěn)定性:絕大多數(shù)用例應(yīng)該是非常穩(wěn)定的,也就是說(shuō)不會(huì)經(jīng)常因?yàn)槌h(huán)境以外的因素掛掉,因?yàn)槿绻谝粋€(gè)測(cè)試項(xiàng)目中有很多不穩(wěn)定的用例的話,測(cè)試結(jié)果就不能很好的反應(yīng)項(xiàng)目質(zhì)量。
分類(lèi)清晰:有相關(guān)性的用例應(yīng)寫(xiě)到一個(gè)模塊或一個(gè)測(cè)試類(lèi)里,這樣做即方便維護(hù),又提高了報(bào)告的可讀性。
測(cè)試工具類(lèi)
這個(gè)可以根據(jù)項(xiàng)目情況去做,力求簡(jiǎn)化一些類(lèi)庫(kù)的使用,數(shù)據(jù)庫(kù)訪問(wèn)、日期時(shí)間、序列化與反序列化等數(shù)據(jù)處理,或者封裝一些常用操作,如隨機(jī)生成訂單號(hào)等等,以提高腳本編寫(xiě)效率。
測(cè)試數(shù)據(jù)管理
常見(jiàn)的方式有寫(xiě)在代碼里、寫(xiě)在配置文件里(xml、yaml、json、.py、excel等)、寫(xiě)在數(shù)據(jù)庫(kù)里等,該處沒(méi)有什么好推薦的,建議根據(jù)個(gè)人喜好,怎么方便怎么來(lái)就可以。
八、pithy 測(cè)試框架介紹
pithy意為簡(jiǎn)潔有力的,意在簡(jiǎn)化自動(dòng)化接口測(cè)試,提高測(cè)試效率。
項(xiàng)目地址:https://github.com/yuyu1987/pithy-test
幫助文檔:http://pithy-test.readthedocs.io/
目前實(shí)現(xiàn)的功能如下:
一鍵生成測(cè)試項(xiàng)目
http client封裝
thrift接口封裝
簡(jiǎn)化配置文件使用
優(yōu)化JSON、日期等工具使用
編寫(xiě)測(cè)試用例推薦使用 pytest(https://docs.pytest.org/),pytest?提供了很多測(cè)試工具以及插件(http://plugincompat.herokuapp.com/),可以滿足大部分測(cè)試需求。
安裝pip install pithy-test
pip install pytest
使用
一鍵生成測(cè)試項(xiàng)目>>> ?pithy-cli init
請(qǐng)選擇項(xiàng)目類(lèi)型,輸入api或者app: api
請(qǐng)輸入項(xiàng)目名稱(chēng),如pithy-api-test: pithy-api-test
開(kāi)始創(chuàng)建pithy-api-test項(xiàng)目
開(kāi)始渲染...
生成 api/.gitignore ? ? ? ? ? ? ? ? ? [√]
生成 api/apis/__init__.py ? ? ? ? ? ? [√]
生成 api/apis/pithy_api.py ? ? ? ? ? ?[√]
生成 api/cfg.yaml ? ? ? ? ? ? ? ? ? ? [√]
生成 api/db/__init__.py ? ? ? ? ? ? ? [√]
生成 api/db/pithy_db.py ? ? ? ? ? ? ? [√]
生成 api/README.MD ? ? ? ? ? ? ? ? ? ?[√]
生成 api/requirements.txt ? ? ? ? ? ? [√]
生成 api/test_suites/__init__.py ? ? ?[√]
生成 api/test_suites/test_login.py ? ?[√]
生成 api/utils/__init__.py ? ? ? ? ? ?[√]
生成成功,請(qǐng)使用編輯器打開(kāi)該項(xiàng)目
生成項(xiàng)目樹(shù):>>> tree pithy-api-test
pithy-api-test
├── README.MD
├── apis
│ ? ├── __init__.py
│ ? └── pithy_api.py
├── cfg.yaml
├── db
│ ? ├── __init__.py
│ ? └── pithy_db.py
├── requirements.txt
├── test_suites
│ ? ├── __init__.py
│ ? └── test_login.py
└── utils
└── __init__.py
4 directories, 10 files
調(diào)用 HTTP 登錄接口示例from pithy import request@request(url='http://httpbin.org/post', method='post')def post(self, key1='value1'):
"""
post method
"""
data = { ? ? ? ?'key1': key1
} ? ?return dict(data=data)# 使用response = post('test').to_json() ? ? # 解析json字符,輸出為字典response = post('test').json ? ? ? ? ?# 解析json字符,輸出為字典response = post('test').to_content() ?# 輸出為字符串response = post('test').content ? ? ? # 輸出為字符串response = post('test').get_cookie() ?# 輸出cookie對(duì)象response = post('test').cookie ? ? ? ?# 輸出cookie對(duì)象# 結(jié)果取值, 假設(shè)此處response = {'a': 1, 'b': { 'c': [1, 2, 3, 4]}}response = post('13111111111', '123abc').jsonprint response.b.c ? # 通過(guò)點(diǎn)號(hào)取值,結(jié)果為[1, 2, 3, 4]print response('$.a') # 通過(guò)object path取值,結(jié)果為1for i in response('$..c[@>3]'): # 通過(guò)object path取值,結(jié)果為選中c字典里大于3的元素
print i
優(yōu)化 JSON、字典使用# 1、操作JSON的KEYfrom pithy import JSONProcessor
dict_data = {'a': 1, 'b': {'a': [1, 2, 3, 4]}}
json_data = json.dumps(dict_data)
result = JSONProcessor(json_data)print result.a ? ? # 結(jié)果:1print result.b.a ? # 結(jié)果:[1, 2, 3, 4]# 2、操作字典的KEYdict_data = {'a': 1, 'b': {'a': [1, 2, 3, 4]}}
result = JSONProcessor(dict_data)print result.a ? ? # 1print result.b.a ? # [1, 2, 3, 4]# 3、object path取值raw_dict = { ? ?'key1':{ ? ? ? ?'key2':{ ? ? ? ? ? ?'key3': [1, 2, 3, 4, 5, 6, 7, 8]
}
}
}
jp = JSONProcessor(raw_dict)for i in jp('$..key3[@>3]'): ? ?print i# 4、其它用法dict_1 = {'a': 'a'}
json_1 = '{"b": "b"}'jp = JSONProcessor(dict_1, json_1, c='c')
print(jp)
更多使用方法:http://pithy-test.readthedocs.io/
九、總結(jié)
在本文中,我們以提高腳本開(kāi)發(fā)效率為前提,一步一步打造了一個(gè)簡(jiǎn)易的測(cè)試框架。
但因水平所限,并未涉及測(cè)試數(shù)據(jù)初始化清理、測(cè)試中如何 MOCK 等話題,前路依然任重而道遠(yuǎn),希望給大家一個(gè)啟發(fā),不足之處還望多多指點(diǎn),非常感謝。
「閱讀原文 」查看本場(chǎng) Chat 交流實(shí)錄
總結(jié)
以上是生活随笔為你收集整理的python自动化测试脚本后端_基于 python 的接口自动化测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python调用什么函数实现对文件内容的
- 下一篇: 操作系统的线程和进程的区别_进程,线程,