? 上篇博客中講述了什么是接口測試,已經自動化接口測試流程,這篇博客總結如何實現接口自動化測試
(一)requests
requests庫是Python對HTTP通信的一個工具,將http協議操作封裝成簡單的接口,能夠讓我們高效的編寫各種網絡自動化任務
安裝requests庫
我們可以通過pip命令來進行安裝,在控制板中輸入
后面的版本號可以不必一致,可以去官網查看
我們看上面successfully就是安裝成功了
那我們也可以通過另一個命令來查看當前項目有什么庫
接下來我們看如何使用這個庫
首先就是獲取一個http中的get請求到指定url
我們看一下這個方法的參數,url都不陌生就是我們要請求的網址,params就是我們的參數類似于我們在postman中這個位置填的數據
**kwargs是常見可選參數,就比如我們可以在里面設置請求頭,cookie等
使用時只需要調用get接口
傳入對應參數,就會給我們返回一個response對象,該對象包含了服務器給我們返回的一系列信息
當然之前我們學習請求形式不止get請求還有post,put等請求,我們也可以調用對應方法來進行url請求
我們再點進去看他們的源碼
我們發現調用的都是request方法,所以我們想訪問url時,也可以直接調用request方法,只不過需要我們手動去傳入method用什么方式訪問
我們這里舉個實例,我們選擇用一個博客詳情的接口來舉例
其實和我們使用postman類似
但在上面我們傳遞參數發現,可以使用params,json,data來傳參
那么三者有什么區別?
parasms:使用params傳參,參數是在url上體現的傳遞的是簡單的鍵值對,常用于get請求來獲取對應數據,因為可見所以她不適合傳入敏感數據
data:使用data傳參,參數在data中,常用于put或者post請求傳遞的是表單類型的數據,但是data是不支持嵌套的。鍵值對用?&
?連接
json:使用json傳參,參數在data中,常用于put或者post請求傳遞的是json的數據,json是支持嵌套的
上面就是request庫的一些操作及使用, requests庫主要是發送http請求,但是對于測試執行和管理就沒怎么涉及到,接下來我們會使用pytest來完成測試的組織,執行,管理功能。
(二)pytest
我們這里選擇使用pytest接口來實現自動化接口測試,除了pytest我們也可使用其他框架比如unnittest或者robot framework等
我們選擇pytest是因為她的語法較為簡單,且插件比較多,我們可以通過下載插件來完成各種功能
pytest的安裝
我們安裝pytest有個版本對應表,不同版本的pytest有最低適配的python版本
下載操作和剛剛我們下載requests操作類似,都是使用pip install操作來下載
同樣我們可以使用pip list來看
安裝好后,我們來看一下有沒有pytest框架對于代碼編寫的區別
有pytest
沒有pytest
我們發現如果沒有pytest框架,我們要執行一個方法,需要一個main函數,并在函數中調用,若我們有了pytest框架,我們可以直接運行該方法,但是我們需要遵守pytest的命名規范,下面就來說一下怎樣命名是可以被pytest識別到的
pytest框架默認命名格則
文件名:文件名需要以test_**來命名或者**_test來命名
測試類:測試類必須以Test開頭,并且不能有__init__方法
測試方法:測試方法必須以test**開頭
init和構造方法
注:測試類中不推薦有init方法(類似于java中的構造方法),但是我們可以定義
之所以不推薦是因為init方法是無法訪問pytest fixtures的(執行順序為:init方法->fixtures->其他方法)
而且構造函數只負責初始化并沒有對應清理機制,且每次測試方法都會創建新的實例。
這一點要做一下區分,此時在pytest中的init方法和構造方法是有很大區別的
就比如我們局一個簡單的例子,我們在python正常類中寫一個init方法和一個類方法,之后我們實例化一個對象并多次調用類方法
我們發現只會調用一次構造函數,在多次調用方法時,也不會再調用構造函數,而當我們在pytest中創建init方法,我們再來看一下
他的輸出是這樣的
init方法在每次測試方法執行前都會重新調用一次(這是因為每個測試方法都是新的實例,測試之間不共享狀態)
以上就是我們為什么不推薦使用init方法,為了處理init方法初始化的作用,pytest給我們提供了其他的初始化方法比如:setup/teardowm或者使用fixture? 這些在之后我們也會再講
注:上面在pytest中使用init方法僅限pytest 4之后的版本,在pytest 4之前版本,是禁止任何類有init方法的
pytest命令參數
pytest提供了很多命令行來控制測試的執行,以下是一些常用的命令航參數以及說明
pytest:在當前目錄和其子目錄下找到符合命名規范的類和方法并運行測試
pytest -v:增加輸出的詳細程序
pytest -s:顯示測試中的print語句
pytest test_module.py :運行指定的測試模塊
pytest test_dir:運行指定目錄下的所有測試
還有一些其他的命令
我們在自己項目中使用pytest命令如下:
會自動執行符合pytest命名規則的方法
但是我們看顯示框,發現信息很少,如果我們要顯示更全面的信息,需要使用-v
但是上面并沒有打印出111,這是因為如果我們想顯示print語句,我們需要-s
當我們想指定測試方法執行時,我們需要手動指定,或者手動點擊
手動指定
我們發現每次要執行指令時,我們要手動輸入很長的一段命令,如何解決這個問題呢?
我們就需要把相關配置參數,統一放到pytest配置文件中
pytest配置文件
需要手動在當前項目下創建pytest.ini文件(文本文件),以下時一些常見的配置選項
在配置文件中我們可以這樣指定
這樣就是說明,我們默認在后面加上-vs,搜索的py文件為test_*,搜索的類為Test*
此時我們執行pytest,我們發現可以自動打印出原來pytest -vs才能執行出的表現
當我們更改搜索的py文件名稱時我們來看看表現
但是同時.ini是不區分大小寫的,如果我們想區分大小寫,我們可以使用yml文件(之后會說)
前后置方法
上面我們說pytest里面不推薦有些版本甚至不能使用init初始化
那如果我們想執行測試用例前后執行一些額外操作,我們就需要用pytest提供的三種額外方法做前后置操作
setup_method和teardown_method:這兩個方法用于類中每個測試方法的前置與后置操作
setup_class和teardowm_class:這兩個方法用于整個測試類的前置和后置操作
fixture:使用fixture是比較推薦到方式,之后會詳細說到fixture的使用
setup_method和teardown_method我們來舉個例子
def setup_method(self):print("setup method")def teardown_method(self):print("teardown method")def test_01(self):print("True example")def test_02(self):print("True example02")
就比如上述代碼,我們使用setup_method和teardown_method
我們看現象,在測試方法前我們調用setup_method方法,在測試方法后調用teardown_method方法,每個測試方法都會進行一次調用
setup_class和teardown_class我們同樣舉個例子看現象
def test_01(self):print("True example01")def test_02(self):print("True example02")def setup_class(self):print("setup method")def teardown_class(self):print("teardown method")
我們看到在一個類內,只會調用一次setup/teardown方法
斷言
斷言能夠幫我們檢測程序的狀態是否符合我們的預期,如果斷言失敗,那么python解釋器會給我們拋出一個AssertionError異常,pytest中允許我們使用python中的斷言語句來驗證預期和值
條件必須是一個布爾表達式,錯誤信息選填
這里簡單寫幾個示例
class Test01():#斷言整數a=1b=2assert a==b#斷言字符串s1="str"assert s1=="str"#斷言列表expectList=[1,'aaa',3.21]realList=[1,'aaa',3.21]assert expectList==realList#斷言元組expectTuple=(1,'aaa',3.21)realTuple=(1,'aaa',3.21)assert expectTuple==realTuple#斷言字典expectDict={'a':1,'b':2,'c':3}realDict={'a':1,'b':2,'c':3}assert expectDict==realDict#斷言集合expectSet={'a','b','c'}realSet={'a','b','c'}assert expectSet==realSet
在執行后,我們看到報錯信息還是非常的明顯的,會告訴我們那個斷言錯誤了
這樣我們可以利用斷言來判斷接口的返回值是否符合我們的預期
def test1():url = "http://jsonplaceholder.typicode.com/posts/1"r = requests.get(url=url)expect_data = {"userId": 1,"id": 1,"title": "sunt aut facere repellat provident occaecati excepturi optio
reprehenderit","body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et
cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt
rem eveniet architecto"}print(r.json())assert r.json() == expect_dataassert r.json()['userId'] == 1
如果結果不符合預期就會斷言失敗
參數化
上述的代碼,所有參數都是固定的,那么有沒有一種方式可以像方法一樣傳參,讓過程更加靈活可控呢?
我們可以用pytest內置的pytest.mark.parametrize來對測試函數的參數進行參數化
直接看代碼
@pytest.mark.parametrize("data",(1,2,3,4,5))def test_01(self,data):print("data: ",data)
? 我們給方法設定一個參數data,然后通過pytest
@pytest.mark.parametrize("data,result",[(3+5,8),(1+2,3)])def test_01(self,data,result):assert data==result
.mark.parametrize中第一個參數為要傳的參數,第二個為參數的值
我們不止可以設定一個參數,也可以設置多個參數
代碼如下:
此外除了可以在方法上使用這個參數化之外,在類上同樣可以使用參數化
@pytest.mark.parametrize("data,result", [(3 + 5, 8), (1 + 2, 3)])
class Test01():def test_01(self, data, result):assert data == resultdef test_02(self, data, result):print(data,result)
現象如下,我們發現都會執行到
此時可能就會有疑問,當我們設定了同樣的參數在類和方法上,會發生什么?可以來執行看下
@pytest.mark.parametrize("data,result", [(3 + 5, 8), (1 + 2, 3)])
class Test01():def test_01(self, data, result):assert data == result@pytest.mark.parametrize("data,result", [(1 + 5, 6), (2 + 2, 4)])def test_02(self, data, result):print(data,result)
上述告訴我們多次嘗試對同一個參數進行重復參數化問題,解決辦法也很簡單
1.我們可以把類層和方法層參數合并到同一個層級
2.類層和方法層使用不同的參數名
除了可以手動指定參數外,我們也可以使用方法返回值來當作參數,只需要把手動指定參數的地方換成函數調用即可,類似于這樣
注:當我們設定了參數化找不到對應參數時,是會報錯的

fixture
用來提供測試函數所需要的資源或者上下文,有點類似于AOP面向切面編程的思想
以下是fixture的一些概念及使用場景
我們來看fixture的基礎使用
class Test01():def fixture01(self):print("fixture1111")def test02(self):Test01.fixture01(self)print("test02")
class Test01():@pytest.fixture()def fixture01(self):print("fixture1111")def test02(self,fixture01):# Test01.fixture01(self)print("test02")
我們看上述兩個代碼,第一種是在第二個方法中調用第一個方法
而第二種方法,是直接將函數名作為參數進行調用
我們看結果都是一樣的
當然也可以在第一種的fixture方法上加上注解,也可以正常使用
那傳到參數和方法中調用有什么區別呢?
除了基本使用fixture,也可以嵌套的使用fixture
class Test01():@pytest.fixture()def fixture01(self):return "01"@pytest.fixture()def est02(self,fixture01):# Test01.fixture01(self)return fixture01def test03(self, est02):# Test01.fixture01(self)print(est02)
與函數嵌套很類似
不止嵌套,我們同樣可以在參數列表中調用多個fixture注解修飾的函數
class Test01():@pytest.fixture()def fixture01(self):return "01"@pytest.fixture()def est02(self,fixture01):# Test01.fixture01(self)return "010203"def test03(self, fixture01,est02):# Test01.fixture01(self)assert fixture01 in est02
fixture不止傳參提供資源這一個用處,之前說過它可以用作于上下文
此時我們需要在代碼中加上yield,這個主要是為了在我們運行測試時,確保它能夠正確的自我清理,以便他不會干擾到其他的測試
我們使用yield而不是return,這樣我們可以運行一些代碼后,把對象返回給其他請求方法
但與return不同的是,該fixture的任何拆解代碼要放在yield之后
一旦pytest確定了fixture,他會運行所有的fixture知道返回或者yield,然后執行下一個fixture重復此工作
測試完成后,pytest會逆向遍歷fixture,對于每個yield后的fixture,運行yield語句之后的代碼
class Test01():@pytest.fixture()def fixture01(self):print("start")yieldprint("stop")def test03(self, fixture01):# Test01.fixture01(self)print("test03")
我們預期的表現是先打印對應方法中調用的fixture在yield前的代碼,然后執行函數體,最后執行yield后的代碼,看現象
帶參數的fixture
看下這個注解的源碼,我們看到有很多參數,但是我們上面在使用時,暫時沒涉及到,這里來講一下參數都有什么用
scope:
function:每個測試函數會調用fixture(也是默認的值)
class:在同一個測試類中共享這個fixture
class Test01():@pytest.fixture(scope="class")def fixture01(self):print("start")yieldprint("stop")def test05(self):# Test01.fixture01(self)print("test03")def test03(self, fixture01):# Test01.fixture01(self)print("test03")def test04(self):# Test01.fixture01(self)print("test04")
按順序先執行test05方法,因不涉及到fixture方法所以還沒有調用,到了test03,先執行fixture方法中yield前的部分,等到class結束后執行yield后的部分
module:在一個文件里共享這個fixture
session:整個測試會話中共享這個fixture
autouse:默認參數為false,代表我們需要顯示傳入,才會調用,如果設置為true,就代表每個測試函數都會自動的調用fixture
params:用于參數化,fixture支持列表,每個參數都會讓fixture執行一次,類似for循環
ids:與params搭配使用,為每個參數化實例指定標識符
類似這樣
@pytest.fixture(params=[1, 2, 3],ids=["測試值1", "測試值2", "測試值3"]
)
def number(request):return request.param
name:用來給fixture方法設定一個名稱,如果使用name,則在測試函數中需要使用這個名稱來引用fixture
剩下的命令,下一篇再說