Python+Requests+Pytest+YAML+Allure實現(xiàn)接口自動化
作者:wintest
鏈接:https://www.cnblogs.com/wintest/p/13423231.html
本項目實現(xiàn)接口自動化的技術(shù)選型:Python+Requests+Pytest+YAML+Allure?,主要是針對之前開發(fā)的一個接口項目來進(jìn)行學(xué)習(xí),通過 Python+Requests 來發(fā)送和處理HTTP協(xié)議的請求接口,使用 Pytest 作為測試執(zhí)行器,使用 YAML 來管理測試數(shù)據(jù),使用 Allure 來生成測試報告。
接口項目開發(fā)學(xué)習(xí):
使用Flask開發(fā)簡單接口(1)--GET請求接口(https://www.cnblogs.com/wintest/p/12728095.html)?
使用Flask開發(fā)簡單接口(2)--POST請求接口(https://www.cnblogs.com/wintest/p/12731508.html)?
使用Flask開發(fā)簡單接口(3)--引入MySQL (https://www.cnblogs.com/wintest/p/12741772.html)?
使用Flask開發(fā)簡單接口(4)--借助Redis實現(xiàn)token驗證(https://www.cnblogs.com/wintest/p/12777340.html)
使用Flask開發(fā)簡單接口(5)--數(shù)據(jù)加密處理(https://www.cnblogs.com/wintest/p/12780090.html)
項目說明
本項目在實現(xiàn)過程中,把整個項目拆分成請求方法封裝、HTTP接口封裝、關(guān)鍵字封裝、測試用例等模塊。
首先利用Python把HTTP接口封裝成Python接口,接著把這些Python接口組裝成一個個的關(guān)鍵字,再把關(guān)鍵字組裝成測試用例,而測試數(shù)據(jù)則通過YAML文件進(jìn)行統(tǒng)一管理,然后再通過Pytest測試執(zhí)行器來運行這些腳本,并結(jié)合Allure輸出測試報告。
當(dāng)然,如果感興趣的話,還可以再對接口自動化進(jìn)行Jenkins持續(xù)集成。
GitHub項目源碼地址:https://github.com/wintests/pytestDemo
項目結(jié)構(gòu)
api ====>> 接口封裝層,如封裝HTTP接口為Python接口 common ====>> 各種工具類 core ====>> requests請求方法封裝、關(guān)鍵字返回結(jié)果類 config ====>> 配置文件 data ====>> 測試數(shù)據(jù)文件管理 operation ====>> 關(guān)鍵字封裝層,如把多個Python接口封裝為關(guān)鍵字 pytest.ini ====>> pytest配置文件 requirements.txt ====>> 相關(guān)依賴包文件 testcases ====>> 測試用例
請求方法封裝
在?core/rest_client.py?文件中,對?Requests?庫下一些常見的請求方法進(jìn)行了簡單封裝,以便調(diào)用起來更加方便。
class?RestClient():
????def?__init__(self,?api_root_url):
????????self.api_root_url?=?api_root_url
????????self.session?=?requests.session()
????def?get(self,?url,?**kwargs):
????????return?self.request(url,?"GET",?**kwargs)
????def?post(self,?url,?data=None,?json=None,?**kwargs):
????????return?self.request(url,?"POST",?data,?json,?**kwargs)
????def?put(self,?url,?data=None,?**kwargs):
????????return?self.request(url,?"PUT",?data,?**kwargs)
????def?delete(self,?url,?**kwargs):
????????return?self.request(url,?"DELETE",?**kwargs)
????def?patch(self,?url,?data=None,?**kwargs):
????????return?self.request(url,?"PATCH",?data,?**kwargs)
????def?request(self,?url,?method,?data=None,?json=None,?**kwargs):
????????url?=?self.api_root_url?+?url
????????headers?=?dict(**kwargs).get("headers")
????????params?=?dict(**kwargs).get("params")
????????files?=?dict(**kwargs).get("params")
????????cookies?=?dict(**kwargs).get("params")
????????self.request_log(url,?method,?data,?json,?params,?headers,?files,?cookies)
????????if?method?==?"GET":
????????????return?self.session.get(url,?**kwargs)
????????if?method?==?"POST":
????????????return?requests.post(url,?data,?json,?**kwargs)
????????if?method?==?"PUT":
????????????if?json:
????????????????#?PUT?和?PATCH?中沒有提供直接使用json參數(shù)的方法,因此需要用data來傳入
????????????????data?=?complexjson.dumps(json)
????????????return?self.session.put(url,?data,?**kwargs)
????????if?method?==?"DELETE":
????????????return?self.session.delete(url,?**kwargs)
????????if?method?==?"PATCH":
????????????if?json:
????????????????data?=?complexjson.dumps(json)
????????????return?self.session.patch(url,?data,?**kwargs)HTTP接口 封裝為 Python接口
在?api/user.py?文件中,將上面封裝好的HTTP接口,再次封裝為不同的Python接口。不同的Python接口,會處理不同URL下的請求。
class?User(RestClient):
????def?__init__(self,?api_root_url,?**kwargs):
????????super(User,?self).__init__(api_root_url,?**kwargs)
????def?list_all_users(self,?**kwargs):
????????return?self.get("/users",?**kwargs)
????def?list_one_user(self,?username,?**kwargs):
????????return?self.get("/users/{}".format(username),?**kwargs)
????def?register(self,?**kwargs):
????????return?self.post("/register",?**kwargs)
????def?login(self,?**kwargs):
????????return?self.post("/login",?**kwargs)
????def?update(self,?user_id,?**kwargs):
????????return?self.put("/update/user/{}".format(user_id),?**kwargs)
????def?delete(self,?name,?**kwargs):
????????return?self.post("/delete/user/{}".format(name),?**kwargs)關(guān)鍵字返回結(jié)果類
在?core/result_base.py?下,定義了一個空類?ResultBase?,該類主要用于自定義關(guān)鍵字返回結(jié)果。
class?ResultBase():
????pass
"""
自定義示例:
result?=?ResultBase()
result.success?=?False
result.msg?=?res.json()["msg"]
result.response?=?res
"""`?
在多流程的業(yè)務(wù)場景測試下,通過自定義期望保存的返回數(shù)據(jù)值,以便更好的進(jìn)行斷言。
關(guān)鍵字封裝
關(guān)鍵字應(yīng)該是具有一定業(yè)務(wù)意義的,在封裝關(guān)鍵字的時候,可以通過調(diào)用多個Python接口來完成。在某些情況下,比如測試一個充值接口的時候,在充值后可能需要調(diào)用查詢接口得到最新賬戶余額,來判斷查詢結(jié)果與預(yù)期結(jié)果是否一致,那么可以這樣來進(jìn)行測試:
1, 首先,可以把? 充值-查詢?的操作封裝為一個關(guān)鍵字,在這個關(guān)鍵字中依次調(diào)用充值和查詢的接口,并可以自定義關(guān)鍵字的返回結(jié)果。2, 接著,在編寫測試用例的時候,直接調(diào)用關(guān)鍵字來進(jìn)行測試,這時就可以拿到關(guān)鍵字返回的結(jié)果,那么斷言的時候,就可以直接對關(guān)鍵字返回結(jié)果進(jìn)行斷言。
測試用例層
根據(jù)用例名分配測試數(shù)據(jù)
測試數(shù)據(jù)位于?data?文件夾下,在這里使用?YAML?來管理測試數(shù)據(jù),同時要求測試數(shù)據(jù)中第一層的名稱,需要與測試用例的方法名保持一致,如?test_get_all_user_info?、test_delete_user。
test_get_all_user_info:
??#?期望結(jié)果,期望返回碼,期望返回信息
??#?except_result,?except_code,?except_msg
??-?[True,?0,?"查詢成功"]
省略
test_delete_user:
??#?刪除的用戶名,期望結(jié)果,期望返回碼,期望返回信息
??#?username,?except_result,?except_code,?except_msg
??-?["測試test",?True,?0,?"刪除用戶信息成功"]
??-?["wintest3",?False,?3006,?"該用戶不允許刪除"]
這里借助?fixture?方法,我們就能夠通過?request.function.__name__?自動獲取到當(dāng)前執(zhí)行用例的函數(shù)名?testcase_name?,當(dāng)我們傳入測試數(shù)據(jù)?api_data?之后,接著便可以使用?api_data.get(testcase_name)?來獲取到對應(yīng)用例的測試數(shù)據(jù)。
import?pytest
from?testcases.conftest?import?api_data
@pytest.fixture(scope="function")
def?testcase_data(request):
????testcase_name?=?request.function.__name__
????return?api_data.get(testcase_name)`
數(shù)據(jù)準(zhǔn)備和清理
在接口自動化中,為了保證用例可穩(wěn)定、重復(fù)地執(zhí)行,我們還需要有測試前置操作和后置操作,即數(shù)據(jù)準(zhǔn)備和數(shù)據(jù)清理工作。
@pytest.fixture(scope="function")
def?delete_register_user():
????"""注冊用戶前,先刪除數(shù)據(jù),用例執(zhí)行之后,再次刪除以清理數(shù)據(jù)"""
????del_sql?=?base_data["init_sql"]["delete_register_user"]
????db.execute_db(del_sql)
????logger.info("注冊用戶操作:清理用戶--準(zhǔn)備注冊新用戶")
????logger.info("執(zhí)行前置SQL:{}".format(del_sql))
????yield?#?用于喚醒?teardown?操作
????db.execute_db(del_sql)
????logger.info("注冊用戶操作:刪除注冊的用戶")
????logger.info("執(zhí)行后置SQL:{}".format(del_sql))
在這里,以用戶注冊用例為例。對于前置操作,我們應(yīng)該準(zhǔn)備一條刪除SQL,用于將數(shù)據(jù)庫中已存在的相同用戶刪除,對于后置操作,我們應(yīng)該再執(zhí)行刪除SQL,確保該測試數(shù)據(jù)正常完成清理工作。
在測試用例中,我們只需要在用例上傳入?fixture?的函數(shù)參數(shù)名?delete_register_user?,這樣就可以調(diào)用?fixture?實現(xiàn)測試前置及后置操作。當(dāng)然,也可以使用pytest裝飾器?@pytest.mark.usefixtures()?來完成,如:
@pytest.mark.usefixtures("delete_register_user")`?
Allure用例描述
在這里,我們結(jié)合 Allure 來實現(xiàn)輸出測試報告,同時我們可以使用其裝飾器來添加一些用例描述并顯示到測試報告中,以便報告內(nèi)容更加清晰、直觀、可讀。如使用?@allure.title()?自定義報告中顯示的用例標(biāo)題,使用?@allure.description()?自定義用例的描述內(nèi)容,使用?@allure.step()?可在報告中顯示操作步驟,使用?@allure.issue()?可在報告中顯示缺陷及其鏈接等。
@allure.step("步驟1?==>>?注冊用戶")
def?step_1(username,?password,?telephone,?sex,?address):
????logger.info("步驟1?==>>?注冊用戶?==>>?{},?{},?{},?{},?{}".format(username,?password,?telephone,?sex,?address))
@allure.severity(allure.severity_level.NORMAL)
@allure.epic("針對單個接口的測試")
@allure.feature("用戶注冊模塊")
class?TestUserRegister():
????"""用戶注冊"""
[email protected]("用例--注冊用戶信息")
[email protected]("該用例是針對獲取用戶注冊接口的測試")
[email protected]("https://www.cnblogs.com/wintest",?name="點擊,跳轉(zhuǎn)到對應(yīng)BUG的鏈接地址")
[email protected]("https://www.cnblogs.com/wintest",?name="點擊,跳轉(zhuǎn)到對應(yīng)用例的鏈接地址")
[email protected](
????????"測試數(shù)據(jù):【?{username},{password},{telephone},{sex},{address},{except_result},{except_code},{except_msg}】")
[email protected]
[email protected]("username,?password,?telephone,?sex,?address,?except_result,?except_code,?except_msg",
?????????????????????????????api_data["test_register_user"])
[email protected]("delete_register_user")
????def?test_delete_user(self,?login_fixture,?username,?except_result,?except_code,?except_msg):
省略`?項目部署
首先,下載項目源碼后,在根目錄下找到?requirements.txt?文件,然后通過 pip 工具安裝 requirements.txt 依賴,執(zhí)行命令:
pip3?install?-r?requirements.txt`?
接著,修改?config/setting.ini?配置文件,在Windows環(huán)境下,安裝相應(yīng)依賴之后,在命令行窗口執(zhí)行命令:
pytest
注意:因為我這里是針對自己的接口項目進(jìn)行測試,如果想直接執(zhí)行我的測試用例來查看效果,需要提前部署上面提到的接口項目。
測試報告效果展示
在命令行執(zhí)行命令:pytest?運行用例后,會得到一個測試報告的原始文件,但這個時候還不能打開成HTML的報告,還需要在項目根目錄下,執(zhí)行命令啟動?allure?服務(wù):
#?需要提前配置allure環(huán)境,才可以直接使用命令行
allure?serve?./report`
最終,可以看到測試報告的效果圖如下:

