一、為什么要用Pytest來做接口自動化
1.Pytest的用途及優點
Pytest目前是自動化業界非常主流的一個自動化測試框架,它本質是Python的一個第三方單元測試庫。和unittest一樣,主要是用來管理自動化測試用例的執行的,比如用例執行,用例分組,執行日志輸出等等。
Pytest的優點:
1.他可以自動的識別測試模塊(測試文件)、測試類以及測試函數,規則很簡單:
文件名:格式為test _ *.py或* _test.py的文件
類:Test開頭
測試函數/方法:擁有test前綴的測試函數或方法
2.模塊化夾具fixture可用來管理各類測試資源
3.對unittest完全兼容
我們知道在pytest出來之前,unittest應用也極為廣泛,因此完全兼容unittest,在想要對自動化框架升級的時候可以節省不少開發成本。
4.Pytest是最能 裝“插”的開源單元測試框架
https://docs.pytest.org/en/latest/reference/plugin_list.html
目前pytest已經有900多個插件,每天都會新增1到2個插件。著名的插件有:
pytest-allure、pytest-rerunfailures、pytest-xdist等等
插件能力:集成功能強大的測試報告組件,實現失敗重跑,並發用例執行等
插件的安裝也非常簡單,基本上就是:pip install 插件名
5.pytest安裝
1)安裝命令
pip install pytest
2)驗證安裝
pytest --version
3)官方文檔
https://docs.pytest.org/en/latest/contents.html
以上這些,充分說明pytest是一個簡單、易用並且功能強大的用例管理框架。
二、接口測試簡介
接口組成元素:
1.接口地址(url+端口+路徑)
2.接口請求方式: post get delete put...
3.接口請求參數
4.響應數據
這里演示使用postman完成登陸接口調用:
然后我們看到登陸成功后,響應報文里頭有個token,這個東西是干嘛的呢,簡單介紹一下:
token用途簡介
登陸系統 = 進醫院大門
token = 綠碼
擁有了綠碼才有進入醫院的權限 = 擁有token才能訪問系統內部的各個頁面
沒有綠碼,不允許進入任何門診 = 沒有token,即使你擁有接口的鏈接,響應也會提示你鑒權失敗
以上只是接口最簡單的一個入門介紹,要真正的學好接口測試,要學的東西很多,比如必須熟悉的入門級的基礎理論:接口通信原理、HTTP網絡協議、接口鑒權機制等等,一節課肯定是沒法熟悉接口自動化的,必須系統的去學習才能完整的掌握
三、用Python代碼實現接口測試
相對於工具,用python做接口自動化的優勢:
擴展性更強,更靈活
python 各種封裝、調用
可集成各種庫和工具,滿足各種需求
allure 報告
jsonpath 報文解析
jenkins 持續集成
。。。
代碼實操:Pytest_intf_advance/base_intf/demo.py
PS:這里的接口地址是自己本地搭建的一個接口測試服務,僅供大家參考,后期有時間再弄個免費的公網接口服務給大家使用
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : demo.py import json import jsonpath import requests from Pytest_intf_advance.base_intf.api_key import ApiKey # 接口請求的模擬 # 數據的生成 data = { 'username': 'admin', 'password': '123456' } # 接口的地址 url = 'http://127.0.0.1:5000/api/login' # 將數據傳遞到對應的接口地址,來實現一次該接口的請求下發並返回響應結果:定義對應的請求方法 res = requests.post(url=url, json=data) # 輸出響應結果:編譯后的內容 print(res.text)
# 返回報文中的某個key,比如msg來獲取相應的值 print(type(res.text)) # 輸出響應結果:字典類型 print(res.json()) print(type(res.json())) # 接口斷言 assert 'success' == res.json()['msg'] # 接口斷言,嵌套字典,{key:value,key:{key:value}} # assert 'changsha' == res.json()['city'] assert 'changsha' == res.json()['adress']['city'] # 那么這里我們就可以用到jsonpath庫,來簡化取值操作 # jsonpath獲取數據的表達式:成功則返回list,失敗則返回false # loads是將json格式的內容轉換為字典的格式 # jsonpath接收的是dict類型的數據 value_list = jsonpath.jsonpath(res.json(), '$..{0}'.format('city')) print(value_list) value = value_list[0] print(value) assert 'changsha' == value # 就有點繁瑣,因此我們引入接口關鍵字封裝,來簡化代碼,封裝api_key # 實例化工具類 ak = ApiKey() assert 'changsha' == ak.get_text(res.text,'city') print(ak.get_text(res.text,'city'))
代碼執行輸出信息:
{ "adress": { "city": "changsha" }, "httpstatus": 200, "info": { "age": 18, "name": "admin" }, "msg": "success", "token": "23657DGYUSGD126731638712GE18271H" } <class 'str'> {'adress': {'city': 'changsha'}, 'httpstatus': 200, 'info': {'age': 18, 'name': 'admin'}, 'msg': 'success', 'token': '23657DGYUSGD126731638712GE18271H'} <class 'dict'> ['changsha'] changsha changsha Process finished with exit code 0
四、接口關鍵字封裝
代碼實操:Pytest_intf_advance/base_intf/api_key.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : api_key.py """ 這是接口關鍵字驅動類,用於提供自動化接口測試的關鍵字方法。 主要實現常用的關鍵字內容,並定義好所有的參數內容即可 """ import json import allure import jsonpath import requests class ApiKey: # 基於jsonpath獲取數據的關鍵字:用於提取所需要的內容 def get_text(self,data,key): # jsonpath獲取數據的表達式:成功則返回list,失敗則返回false # loads是將json格式的內容轉換為字典的格式 # jsonpath接收的是dict類型的數據 dict_data = json.loads(data) value = jsonpath.jsonpath(dict_data,'$..{0}'.format(key)) return value[0] # get請求的封裝:因為params可能存在無值的情況,存放默認None def get(self,url,params=None,**kwargs): return requests.get(url=url,params=params,**kwargs) #post請求的封裝:data也可能存在無值得情況,存放默認None def post(self,url,data=None,**kwargs): return requests.post(url=url,data=data,**kwargs) if __name__ == '__main__': ak = ApiKey() data = { 'username': 'admin', 'password': '123456' } res2 = ak.post(url='http://127.0.0.1:5000/api/login',json=data) print(res2.text)
代碼執行輸出信息:
{ "adress": { "city": "changsha" }, "httpstatus": 200, "info": { "age": 18, "name": "admin" }, "msg": "success", "token": "23657DGYUSGD126731638712GE18271H" }
那么我們第一個接口自動化代碼就寫完了,我們可以看到,上述代碼是不是很松散。沒有以一個用例的形式來進行,那么這時候我們就可以上Pytest了
五、用Pytest框架編寫接口自動化測試代碼
1.基本用例組織
pytest用例運行測規則很簡單,從上往下,完全按你放置的順序來執行
代碼實操:Pytest_intf_advance/pytest_intf/case/test_shopXo.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : test_shopXo.py import pytest import requests from Pytest_intf_advance_V2.base_intf.api_key import ApiKey class Test_ApiCase(): # 登陸接口用例 def test_login(self): url = 'http://127.0.0.1:5000/api/login' userInfo = { 'username': 'admin', 'password': '123456' } res = self.ak.post(url=url, json=userInfo) print(res.text) # 獲取響應中的結果,用於校驗是否成功 msg1 = self.ak.get_text(res.text, 'msg') print(msg1) assert msg1 == 'success' # 查詢用戶信息接口 def test_getuserinfo(self,token_fix): # 1.獲取工具類、token ak, token = token_fix # 2.查詢個人用戶信息 url = 'http://127.0.0.1:5000/api/getuserinfo' headers = { 'token': token } res1 = ak.get(url=url, headers=headers) print(res1.text) name = ak.get_text(res1.text, 'nikename') assert "風清揚" == name if __name__ == '__main__': # -s參數,在控制台輸出打印信息 # -v參數,在控制台輸出詳細信息 pytest.main(['-s','-v','test_shopXo_02.py'])
代碼執行輸出信息:
============================= test session starts ============================= platform win32 -- Python 3.8.10, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- D:\Python38\python.exe cachedir: .pytest_cache metadata: {'Python': '3.8.10', 'Platform': 'Windows-10-10.0.19044-SP0', 'Packages': {'pytest': '6.1.2', 'py': '1.9.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.8.11', 'forked': '1.1.3', 'html': '3.0.0', 'metadata': '1.8.0', 'ordering': '0.6', 'parallel': '0.1.0', 'rerunfailures': '9.1.1', 'xdist': '1.31.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_152'} rootdir: D:\Pytest_intf_advance_V2\pytest_intf\case plugins: allure-pytest-2.8.11, forked-1.1.3, html-3.0.0, metadata-1.8.0, ordering-0.6, parallel-0.1.0, rerunfailures-9.1.1, xdist-1.31.0 collecting ... collected 2 items test_shopXo.py::Test_ApiCase::test_login { "adress": { "city": "changsha" }, "httpstatus": 200, "info": { "age": 18, "name": "admin" }, "msg": "success", "token": "23657DGYUSGD126731638712GE18271H" } success PASSED test_shopXo.py::Test_ApiCase::test_getuserinfo { "data": [ { "nikename": "王五", "openid": "UEHUXUXU78272SDSassDD", "userbalance": 5678.9, "userid": 17890, "username": "admin", "userpoints": 4321 } ], "httpstatus": 200 } PASSED ============================== 2 passed in 0.30s ============================== Process finished with exit code 0
2.接口關聯
實現接口關聯有多種方式:
1)和工具類相似使用setup_class,用的時候加個self就可以了
2)公共變量可以定義一個公共變量放到類里,這樣類中的所有用例都可以讀取到了
那么上面這兩種方式,其實是有局限性的,就是只能在單個類或者單個文件中使用,如果需要在整個項目的多個測試文件中使用,就不行了,現在就給大家着重介紹第三種方式,來實現項目級的token預置
六、fixture+conftest實現項目級token預置
接下來,我們先講講fixture,官方的介紹比較復雜,這邊我個人總結了下。
1.pytest之fixture介紹
fixture是pytest提供的一個簡化的裝飾器,可以輕松的復用已定義的函數邏輯(比如登陸,獲取token,環境數據預置)等操作。
官方介紹:
https://docs.pytest.org/en/latest/explanation/fixtures.html#about-fixtures
概念很簡單,關鍵是如何用,下面給大家編寫一個fixture快速入門的例子
代碼實操:Pytest_intf_advance/base_intf/test_fix/test_quick_exam.py
#coding=utf-8 import pytest # 通過@pytest.fixture聲明這個函數為fixture # 用例前置 @pytest.fixture def first_fix(): # 此處假設有代碼邏輯幾十行,比如執行登陸操作,獲取token之類的 # 返回一個list return ["a"] # 將已聲明為fixture的函數,填寫在參數中,這樣fixture函數會在該函數調用之前調用 # 測試用例 def test_string(first_fix): # 用例步驟 # 使用first_fix返回的list添加元素 first_fix.append("b") print(first_fix) if __name__ == '__main__': pytest.main(['-s'])
代碼執行輸出信息:
============================= test session starts ============================= platform win32 -- Python 3.8.10, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 rootdir: D:\Pytest_intf_advance_V2\base_intf\test_fix plugins: allure-pytest-2.8.11, forked-1.1.3, html-3.0.0, metadata-1.8.0, ordering-0.6, parallel-0.1.0, rerunfailures-9.1.1, xdist-1.31.0 collected 3 items test_quick_exam.py ['a', 'b'] . test_conftest_01\doc1\test_case01.py 開始執行登陸操作 用例一 . test_conftest_01\doc2\test_case02.py 開始執行登陸操作 用例二 . ============================== 3 passed in 0.07s ============================== Process finished with exit code 0
以上就是fixture最基本的概念和用法。這時候,可能有人就想問了,那我想在其他文件中使用這個定義好的fixture怎么辦呢?那這就要介紹下fixture的好基友,conftest.py配置文件了
2.conftest.py
conftest.py是pytest特有的本地測試配置文件,在這個文件中定義的Fixture可以在項目中多個文件使用,conftest.py文件名稱是固定的,pytest會自動識別該文件,只作用於它所在的目錄及子目錄。
同樣的,編寫一個快速入門的代碼例子:
代碼實操:多文件代碼,先看代碼結構
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/conftest.py
#coding=gbk import pytest @pytest.fixture() def fix1(): print("\n開始執行登陸操作")
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/doc1/test_case01.py
#coding=gbk import pytest def test_case02(fix1): print("用例一")
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/doc2/test_case02.py
#coding=gbk import pytest def test_case02(fix1): print("用例二")
Pytest_intf_advance/base_intf/test_fix/test_conftest_01/main_run.py
#coding=gbk import pytest if __name__ == '__main__': pytest.main(['-s'])
運行main_run.py文件
代碼輸出:
collected 2 items doc1\test_case01.py 開始執行登陸操作 用例一 . doc2\test_case02.py 開始執行登陸操作 用例二 . ============================== 2 passed in 0.05s ============================== Process finished with exit code 0
fixture+conftest的搭配是非常的有用的,接下來我們來在接口自動化中實戰一把,來展現一下這對黃金組合的實力
3.接口中應用fix+conftest實現項目級token預置
1)先在conftest中定義fixture
初始化工具類,登陸,並返回工具類對象和token值
代碼實操:多文件代碼,先看代碼結構
Pytest_intf_advance_V2/pytest_intf/conftest.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : conftest.py from random import random from Pytest_intf_advance.base_intf.api_key import ApiKey import pytest # @pytest.fixture(scope='session') # def token_fix(): # a = random() # print(a) # return a #項目級fix,整個項目只初始化一次 @pytest.fixture(scope='session') def token_fix(): # 初始化工具類 ak = ApiKey() # 定義訪問鏈接‘ url = 'http://127.0.0.1:5000/api/login' # 定義請求用戶數據 userInfo = { 'username': 'admin', 'password': '123456' } # 發送post請求 res = ak.post(url=url,json = userInfo) # 獲取token token = ak.get_text(res.text,'token') # 返回多個值 return ak,token
Pytest_intf_advance_V2\pytest_intf\api_keyword\api_key.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : api_key.py """ 這是接口關鍵字驅動類,用於提供自動化接口測試的關鍵字方法。 主要實現常用的關鍵字內容,並定義好所有的參數內容即可 """ import json import allure import jsonpath import requests class ApiKey: # 基於jsonpath獲取數據的關鍵字:用於提取所需要的內容 def get_text(self,data,key): # jsonpath獲取數據的表達式:成功則返回list,失敗則返回false # loads是將json格式的內容轉換為字典的格式 # jsonpath接收的是dict類型的數據 dict_data = json.loads(data) value = jsonpath.jsonpath(dict_data,'$..{0}'.format(key)) return value[0] # get請求的封裝:因為params可能存在無值的情況,存放默認None def get(self,url,params=None,**kwargs): return requests.get(url=url,params=params,**kwargs) #post請求的封裝:data也可能存在無值得情況,存放默認None def post(self,url,data=None,**kwargs): return requests.post(url=url,data=data,**kwargs) if __name__ == '__main__': ak = ApiKey() # res = ak.get(url='http://127.0.0.1:5000/api/getuserinfo',timeout=0.1) # print(res.text) data = { 'username': 'admin', 'password': '123456' } res2 = ak.post(url='http://127.0.0.1:5000/api/login',json=data) print(res2.text)
Pytest_intf_advance_V2\pytest_intf\case\test_shopXo_02.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : test_shopXo.py import pytest import requests from Pytest_intf_advance_V2.base_intf.api_key import ApiKey class Test_ApiCase(): # 查詢用戶信息接口 def test_getuserinfo(self,token_fix): # 1.獲取工具類、token ak, token = token_fix # 2.查詢個人用戶信息 url = 'http://127.0.0.1:5000/api/getuserinfo' headers = { 'token': token } res1 = ak.get(url=url, headers=headers) print(res1.text) name = ak.get_text(res1.text, 'nikename') assert "張三" == name if __name__ == '__main__': # -s參數,在控制台輸出打印信息 # -v參數,在控制台輸出詳細信息 pytest.main(['-s','-v','test_shopXo_02.py'])
代碼輸出:
collecting ... collected 1 item test_shopXo_02.py::Test_ApiCase::test_getuserinfo { "data": [ { "nikename": "張三", "openid": "UEHUXUXU78272SDSassDD", "userbalance": 5678.9, "userid": 17890, "username": "admin", "userpoints": 4321 } ], "httpstatus": 200 } PASSED ============================== 1 passed in 0.20s ============================== Process finished with exit code 0
通過fixture+conftest 這套組合,可以方便的在多個文件中使用同一個token,可以在項目內實現更大范圍內的接口關聯。