python自动化测试脚本后端_基于 python 的接口自动化测试
本文來自作者:孫彥輝?在 GitChat 上精彩分享,「閱讀原文」看看大家和作者交流了哪些問題
一、簡介
本文從一個簡單的登錄接口測試入手,一步步調(diào)整優(yōu)化接口調(diào)用姿勢;
然后簡單討論了一下接口測試框架的要點;
最后介紹了一下我們目前正在使用的接口測試框架?pithy。
期望讀者可以通過本文對接口自動化測試有一個大致的了解。
二、引言
為什么要做接口自動化測試?
在當前互聯(lián)網(wǎng)產(chǎn)品迭代頻繁的背景下,回歸測試的時間越來越少,很難在每個迭代都對所有功能做完整回歸。
但接口自動化測試因其實現(xiàn)簡單、維護成本低,容易提高覆蓋率等特點,越來越受重視。
為什么要自己寫框架呢?
使用 requets + unittest 很容易實現(xiàn)接口自動化測試,而且 requests 的api已經(jīng)非常人性化,非常簡單。
但通過封裝以后(特別是針對公司內(nèi)特定接口),再加上對一些常用工具的封裝,可以進一步提高業(yè)務腳本編寫效率。
三、環(huán)境準備
確保本機已安裝 python2.7 以上版本,然后安裝如下庫:pip install flask
pip install requests
后面我們會使用 flask 寫一個用來測試的接口,使用requests去測試。
四、測試接口準備
下面使用 flask 實現(xiàn)兩個 http 接口,一個登錄,另外一個查詢詳情,但需要登錄后才可以,新建一個 demo.py 文件(注意,不要使用windows記事本),把下面代碼 copy 進去,然后保存、關閉。
接口代碼#!/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
響應如下:* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
大家可以看到服務已經(jīng)起來了。
接口信息
登錄接口
請求url
/login
請求方法
post
請求參數(shù)
| 參數(shù)名稱 ? ? ? ?| 參數(shù)類型 ? | ?參數(shù)說明 ?|
| :————: ? | :——-: ?| :——: ?|
| username ? ? | String | ? 登錄名稱 ? ? |
| password ? ? ? ?| ? String
| ? 登錄密碼 ? |
響應信息
| 參數(shù)名稱 ? ? ? ?| 參數(shù)類型 ? | ?參數(shù)說明 ?|
| :————: ? | :——-: ?| :——: ?|
| code ? ? ? ?| ? Integer ? | ? 結(jié)果code ? |
| msg ? ? ? ?|
String ? | ? 結(jié)果信息 ? |
詳情接口
請求url
/info
請求方法
get
請求 cookies
| 參數(shù)名稱 ? ? ? ?| 參數(shù)類型 ? | ?參數(shù)說明 ?|
| :————: ? | :——-: ?| :——: ?|
| session ? ? | String | ? session ? ? |
響應信息
| 參數(shù)名稱 ? ? ? ?| 參數(shù)類型 ? | ?參數(shù)說明 ?|
| :————: ? | :——-: ?| :——: ?|
| code ? ? ? ?| ? Integer ? | ? 結(jié)果code ? |
| msg ? ? ? ?|
String ? | ? 結(jié)果信息 ? |
| data ? ? ? ?| ? String ? | ? 數(shù)據(jù)信息 ? |
五、編寫接口測試
測試思路
使用 requests [ http://docs.python-requests.org/zh_CN/latest/user/quickstart.html ] 庫模擬發(fā)送 HTTP 請求。
使用 python 標準庫里 unittest 寫測試 case。
腳本實現(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):
"""
測試登錄
"""
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):
"""
測試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)用
寫完這個測試登錄腳本,你或許會發(fā)現(xiàn),在整個項目的測試過程,登錄可能不止用到一次,如果每次都這么寫,會不會太冗余了?
對,確實太冗余了,下面做一下簡單的封裝,把登錄接口的調(diào)用封裝到一個方法里,把調(diào)用參數(shù)暴漏出來,示例腳本如下:#!/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: 用戶名
: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):
"""
測試登錄
"""
response = self.app.login(self.username, self.password) ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
測試獲取詳情信息
"""
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,在這一個版本中,我們不但在把登錄接口的調(diào)用封裝成了一個實例方法,實現(xiàn)了復用,而且還把 host(self.base_url)提取了出來。
但問題又來了,登錄之后,登錄接口的 http 響應會把?session以 cookie 的形式 set 到客戶端,之后的接口都會使用此 session 去請求。
還有,就是在接口調(diào)用過程中,希望可以把日志打印出來,以便調(diào)試或者出錯時查看。
好吧,我們再來改一版。
保持 cookies &增加 log 信息
使用 requests 庫里的同一個 Session 對象 (它也會在同一個 Session 實例發(fā)出的所有請求之間保持 cookie ),即可解決上面的問題,示例代碼如下:#!/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實例
self.session = Session() ? ?def login(self, username, password):
"""
登錄接口
:param username: 用戶名
: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、請求url: \n%s' % url)
print(u'\n2、請求頭信息:')
pprint(self.session.headers)
print(u'\n3、請求參數(shù):')
pprint(data)
print(u'\n4、響應:')
pprint(response) ? ? ? ?return response ? ?def info(self):
"""
詳情接口
"""
url = urljoin(self.base_url, 'info')
response = self.session.get(url).json()
print('\n*****************************************')
print(u'\n1、請求url: \n%s' % url)
print(u'\n2、請求頭信息:')
pprint(self.session.headers)
print(u'\n3、請求cookies:')
pprint(dict(self.session.cookies))
print(u'\n4、響應:')
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):
"""
測試登錄
"""
response = self.app.login(self.username, self.password) ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
def test_info(self):
"""
測試獲取詳情信息
"""
self.app.login(self.username, self.password)
response = self.app.info() ? ? ? ?assert response['code'] == 200
assert response['msg'] == 'success'
assert response['data'] == 'info'
大功告成,我們把多個相關接口調(diào)用封裝到一個類中,使用同一個 requests Session 實例來保持 cookies,并且在調(diào)用過程中打印出了日志,我們所有目標都實現(xiàn)了。
但再看下腳本,又會感覺不太舒服,在每個方法里,都要寫一遍print 1、2、3… 要拼url、還要很多細節(jié)等等。
但其實我們?真正需要做的只是拼出關鍵的參數(shù)(url 參數(shù)、body 參數(shù)或者傳入 headers 信息),可不可以只需定義必須的信息,然后把其它共性的東西都封裝起來呢,統(tǒng)一放到一個地方去管理?
封裝重復操作
來,我們再整理一下我們的需求:
首先,不想去重復做拼接 url 的操作。
然后,不想每次都去手工打印日志。
不想和 requests session 打交道。
只想定義好參數(shù)就直接調(diào)用。
我們先看一下實現(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、請求url
http://127.0.0.1:5000/login
3、請求方法
post
4、請求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、響應結(jié)果
{
"code": 200,
"msg": "success"
}
在這里,我們使用 python 的裝飾器功能,把公共特性封裝到裝飾器中去實現(xiàn)。現(xiàn)在感覺好多了,沒什么多余的東西了,我們可以專注于關鍵參數(shù)的構(gòu)造,剩下的就是如何去實現(xiàn)這個裝飾器了,我們先理一下思路:
獲取裝飾器參數(shù)
獲取函數(shù)/方法參數(shù)
把裝飾器和函數(shù)定義的參數(shù)合并
拼接 url
處理 requests session,有則使用,無則新生成一個
組裝所有參數(shù),發(fā)送http請求并打印日志
因篇幅限制,源碼不再列出,有興趣的同學可以查看已經(jīng)實現(xiàn)的源代碼。
源代碼查看地址:https://github.com/yuyu1987/pithy-test/blob/master/pithy/api.py
七、擴展
http 接口請求的姿勢我們定義好了,我們還可以做些什么呢?
[x] 非HTTP協(xié)議接口
[x] 測試用例編寫
[x] 配置文件管理
[x] 測試數(shù)據(jù)管理
[x] 工具類編寫
[x] 測試報告生成
[x] 持續(xù)集成
[x] 等等等等
需要做的還是挺多的,要做什么不要做什么,或者先做哪個,我覺得可以根據(jù)以下幾點去判斷:
是否有利于提高團隊生產(chǎn)效率?
是否有利于提高測試質(zhì)量?
有沒有現(xiàn)成的輪子可以用?
下面就幾項主要的點進行一下說明,限于篇幅,不再展開了。
測試報告
這個應該是大家最關心的了,畢竟這是測試工作的產(chǎn)出;
目前 python 的主流單元測試框均有 report 插件,因此不建議自己再編寫,除非有特殊需求的。
pytest:推薦使用?pytest-html? 和?allure pytest。
unittest:推薦使用?HTMLTestRunner。
持續(xù)集成
持續(xù)集成推薦使用?Jenkins,運行環(huán)境、定時任務、觸發(fā)運行、郵件發(fā)送等一系列功能均可以在 Jenkins 上實現(xiàn)。
測試用例編寫
推薦遵守如下規(guī)則:
原子性:每個用例保持獨立,彼此不耦合,以降低干擾。
專一性:一個用例應該專注于驗證一件事情,而不是做很多事情,一個測試點不要重復驗證。
穩(wěn)定性:絕大多數(shù)用例應該是非常穩(wěn)定的,也就是說不會經(jīng)常因為除環(huán)境以外的因素掛掉,因為如果在一個測試項目中有很多不穩(wěn)定的用例的話,測試結(jié)果就不能很好的反應項目質(zhì)量。
分類清晰:有相關性的用例應寫到一個模塊或一個測試類里,這樣做即方便維護,又提高了報告的可讀性。
測試工具類
這個可以根據(jù)項目情況去做,力求簡化一些類庫的使用,數(shù)據(jù)庫訪問、日期時間、序列化與反序列化等數(shù)據(jù)處理,或者封裝一些常用操作,如隨機生成訂單號等等,以提高腳本編寫效率。
測試數(shù)據(jù)管理
常見的方式有寫在代碼里、寫在配置文件里(xml、yaml、json、.py、excel等)、寫在數(shù)據(jù)庫里等,該處沒有什么好推薦的,建議根據(jù)個人喜好,怎么方便怎么來就可以。
八、pithy 測試框架介紹
pithy意為簡潔有力的,意在簡化自動化接口測試,提高測試效率。
項目地址:https://github.com/yuyu1987/pithy-test
幫助文檔:http://pithy-test.readthedocs.io/
目前實現(xiàn)的功能如下:
一鍵生成測試項目
http client封裝
thrift接口封裝
簡化配置文件使用
優(yōu)化JSON、日期等工具使用
編寫測試用例推薦使用 pytest(https://docs.pytest.org/),pytest?提供了很多測試工具以及插件(http://plugincompat.herokuapp.com/),可以滿足大部分測試需求。
安裝pip install pithy-test
pip install pytest
使用
一鍵生成測試項目>>> ?pithy-cli init
請選擇項目類型,輸入api或者app: api
請輸入項目名稱,如pithy-api-test: pithy-api-test
開始創(chuàng)建pithy-api-test項目
開始渲染...
生成 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 ? ? ? ? ? ?[√]
生成成功,請使用編輯器打開該項目
生成項目樹:>>> 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對象response = post('test').cookie ? ? ? ?# 輸出cookie對象# 結(jié)果取值, 假設此處response = {'a': 1, 'b': { 'c': [1, 2, 3, 4]}}response = post('13111111111', '123abc').jsonprint response.b.c ? # 通過點號取值,結(jié)果為[1, 2, 3, 4]print response('$.a') # 通過object path取值,結(jié)果為1for i in response('$..c[@>3]'): # 通過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é)
在本文中,我們以提高腳本開發(fā)效率為前提,一步一步打造了一個簡易的測試框架。
但因水平所限,并未涉及測試數(shù)據(jù)初始化清理、測試中如何 MOCK 等話題,前路依然任重而道遠,希望給大家一個啟發(fā),不足之處還望多多指點,非常感謝。
「閱讀原文 」查看本場 Chat 交流實錄
總結(jié)
以上是生活随笔為你收集整理的python自动化测试脚本后端_基于 python 的接口自动化测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python调用什么函数实现对文件内容的
- 下一篇: 斐波那契数列python递归 0、1、1