Python接口測試


1.為什么要做接口測試

 

在面試的時候都會問到為什么要做接口測試以及如何做接口測試的問題,那么我們就稍微來科普一下。

本文討論的接口均是服務級的接口,不是代碼級

接口是什么

在討論為什么要做接口測試之前,我們可以先稍微了解一下接口是什么?

接口可以很不准確的理解成是與資源打交道,這個資源可能是本系統的,也可能是其他系統的。

舉個例子,假如我們在開發1個bug管理系統,該系統需要拿到公司的所有開發和測試人員的信息,這樣開發和測試人員不用注冊都可以登錄進去了,這應該很好理解。

那么這些人員的信息儲存在哪里呢?一般存儲在hr系統里。現在的需求更加明確了,我們要到hr系統中去拿到人員信息,獲取hr系統中的人員資源。

怎么拿呢?很多種方式,可以直接把hr系統的數據庫拷貝一份放到bug管理系統里,不過這樣不好,因為數據的同步會有點麻煩;還可以直接連hr系統的數據庫去查,這樣也不太好,這樣我們就需要了解hr系統的數據存儲結構和邏輯,一旦hr系統的數據字段發生改變,bug管理系統也要去該,以便同步。

比較好的做法是,hr系統暴露一些接口,通過這些接口去獲取人員信息資源,這樣bug系統就不需要關心hr系統的數據存儲實現了。

這些接口可能是這樣的:

  • 登錄的接口,提供人員的用戶名和密碼,去hr系統中判斷該人員是否存在,如果存在驗證用戶名和密碼,如果驗證通過就返回1個token,該token就是這個人員的通行證,通過token可以登錄到bug管理系統中去;
  • 獲取人員信息的接口,返回該人員的職位:測試還是開發,以及用戶名,昵稱等信息;

綜上:接口可以理解成是不同系統或模塊之間資源交流方式;

接口測試實際上是黑盒測試

作為黑盒測試,基本的測試思路是通過輸入和輸出判斷被測系統或者對象的邏輯。

獲取人員的信息,我需要把人員的用戶名傳給hr系統接口,這樣hr系統的接口會返回給我用戶的一些更加具體的信息。這里的輸入是用戶名,輸出是用戶的詳細信息。

為什么要做接口測試

既然是接口獲取和操作資源的方式,而大部分系統和產品中,資源一般都是產品的核心,比如微信核心資源就是通訊錄關系鏈和聊天記錄等,因此資源是必測的。

另外接口中大部分的內容是數據,通過數據的對比我們能推測到系統和產品的邏輯,測接口就是測邏輯。

最后接口中的返回相對單純,不像web頁面,html代碼中有太多ui的東西,ui最不穩定,變化太快,接口相對穩定一點點,但是里面的干擾信息更少,斷言相對容易很多。

接口測試用例怎么寫

還是3a原則,這個我以前的回答里有。

  • A: arrange 初始化測試數據,就是造數據,這里的數據有我們輸入的數據,也有目標接口所涉及的資源,比如hr系統中的用戶信息,我們必須先有幾條人員的詳細信息才能去測獲取人員信息的接口(當然只是正常的流程,我們有時候還需要清掉數據以便測試資源為空的情況);

  • A: act 調用接口,傳入輸入數據;

  • A: assert 斷言, 對返回的資源信息進行斷言,比如獲取用戶信息的接口返回了用戶信息之后,我們要判斷返回的用戶是不是我們想要的那個用戶,我們獲取的是李雷的信息,接口如果返回韓梅梅,那么接口的邏輯就是不對的;

接口測試筆試題

測試教程網接口測試筆試題

有哪些常見的接口

  • 攜程訂飛機票,飛機票的信息一般都是通過各大航空公司的接口拿到的;

  • 淘寶的物流信息,一般淘寶的物流信息都是通過各個物流公司的接口拿到的;

  • 第三方微博客戶端,個人用戶的微博等信息都是通過微博的接口拿到的;

常見的接口測試工具

  • postman: 推薦。基本功能免費。最簡單的基於http接口的調試和測試工具;
  • jmeter:后置處理器配合斷言基本上可以滿足接口測試需求,就是測試報告要做二次開發
  • 自己擼代碼:推薦。配合類似xunit測試框架,基本可以滿足一切需求;
  • soapui: 收費的;
  • insomnia:強力推薦。postman的弱化版,基本功能免費,重要的是工具代碼開源,可以自己改;
  • paw: 強力推薦。mac上最強,淘寶買個授權好像就百把塊錢;

 

2.最簡單的接口長什么樣

接口測試由淺入深的學習才可以盡量減少挫敗感,避免從入門到放棄的悲劇。

從入門到放棄其實最可惜,畢竟花了那么多時間去學習,最后放棄掉,花費掉的青春就一去不復返了。

獲取資源的接口

我們先來看一下獲取資源的接口。

v2ex是一個討論區,也就是大家常說的論壇,這是一個小眾的討論區,基本上只有程序員光顧。

v2ex里有很多的節點,節點可以理解成是討論板塊,比如這個板塊就是討論python技術的。

v2ex提供了一些接口,可以讓我們去獲取v2ex站點的一些資源,比如討論區的信息,熱門帖子等,這很符合互聯網的分享精神。

v2ex的api描述頁面在這里, 文檔相對簡潔,基本上是給看得懂的人看的。

我們仔細研究一下獲取節點信息的接口。這個接口可以獲取指定節點的名字,簡介,URL 及頭像圖片的地址。

下面是這個接口的具體描述

獲得指定節點的名字,簡介,URL 及頭像圖片的地址。

https://www.v2ex.com/api/nodes/show.json Method: GET Authentication: None 接受參數: name: 節點名(V2EX 的節點名全是半角英文或者數字) 例如: https://www.v2ex.com/api/nodes/show.json?name=python 

我們從這個接口文檔里可以獲得哪些信息呢?換一句話說,我們能不能通過接口文檔去了解這個接口是做什么的呢?

接口分析

從上面的描述里,我們可以得到下面一些信息

  • 接口的協議:https協議,也就是更安全版本的http協議;
  • 請求的方法:http協議里定義了一些請求的方法或者叫動詞,這些方法和動詞可以進一步定義請求的目的,比如是獲取資源還是創建資源等;上面的例子里,請求方法是GET;
  • 請求參數:http協議里規定了請求的時候可以傳遞一些參數給服務端,這些參數可以更加具體的描述資源,比如獲取多少個資源,這個資源的名字是什么。上面的例子里,我們可以傳遞name參數指定資源的名稱,比如獲取名字叫python的資源,這里資源就是節點;
  • 鑒權:簡單來說,就是要不要登錄,很顯然,上面的接口是不需要登錄的

合起來,我們應該可以得到這樣的信息:

當我們發送:https://www.v2ex.com/api/nodes/show.json?name=python,這個http的get請求給服務器之后,服務器應該返回相應的資源,那么這時候就需要探討一下

  • 什么是請求

  • 什么是響應

如何查看接口的返回

我們現在已經知道了這個接口的情況了,如何查看接口的返回呢?

我們可以使用一些輔助工具幫助我們進行接口的調用,查看接口返回,最簡單的跨平台調試工具推薦使用postman

 

3.入門級接口測試工具:postman的安裝

 

關於postman

postman是跨平台的接口調試及測試工具,非常適合初學者使用。

安裝postman

下載地址

請選擇相應的版本下載,windows版下載完成后雙擊安裝就好了。

postman的界面

發送第一個請求

我們發送上一節里提到的獲取v2ex節點信息的api。

https://www.v2ex.com/api/nodes/show.json?name=python 

具體步驟如下

postman是如何工作的

從原理圖上可以看出,postman發送請求給服務器,然后從服務器接受響應,最后在postman中展示出來。

服務器響應

上圖就是服務器的響應詳情,這里包含了一些重要信息

  • 狀態碼: 200,表示響應是ok的
  • Body: 返回的主體
  • Headers: 可以簡單的理解為一些鍵值對,對請求的主體起到了補充的作用
  • Time: 響應時間
  • Size: 響應的大小

json字符串

可以看出來,響應的主體是json格式的字符串,那么什么是json格式字符串呢?下一節我們將詳細講解。

 

4.Json簡介

 

官方解釋

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。 易於人閱讀和編寫。同時也易於機器解析和生成。 它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。 JSON采用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 這些特性使JSON成為理想的數據交換語言。

什么是JSON

首先json是字符串。

大家都知道,字符串是用來傳遞信息的。json字符串實際上就是一種規定了格式的字符串,

通過這種格式,我們可以在不同的編程語言之間互相傳遞信息,比如我們可以把javascript的對象轉換成json傳遞給java,這樣java可以反解析出java語言自身代表的對象;同理,我們可以把java對象轉成json,通過解析json,python語言可以把json轉成是自身的dict或者是list,json統一了交流的格式,使得信息可以在不同的語言間順暢傳遞。

JSON解析的簡單例子

比如,我們可以把json字符串轉成python語言的dict

#coding: utf-8 import json json_str = """ { "id" : 90, "name" : "python", "url" : "http://www.v2ex.com/go/python", "title" : "Python", "title_alternative" : "Python", "topics" : 7646, "stars" : 4862, "header" : "這里討論各種 Python 語言編程話題,也包括 Django,Tornado 等框架的討論。這里是一個能夠幫助你解決實際問題的地方。", "footer" : null, "created" : 1278683336, "avatar_mini" : "//v2ex.assets.uxengine.net/navatar/8613/985e/90_mini.png?m=1504080972", "avatar_normal" : "//v2ex.assets.uxengine.net/navatar/8613/985e/90_normal.png?m=1504080972", "avatar_large" : "//v2ex.assets.uxengine.net/navatar/8613/985e/90_large.png?m=1504080972" } """ res = json.loads(json_str) print(res['id']) # 90 print(res['name']) # python print(res['url']) # http://www.v2ex.com/go/python


5.3A原則

3A原則原本是單元測試用例編寫時應該遵循的基本原則,不過我們可以擴展到接口的自動化測試用例編寫中來。

我們首先看一下3A原則在每一層自動化測試中的具體應用。

單元測試用例

  • Arrange: 初始化測試對象或者准備測試數據
  • Act : 調用被測方法
  • Assert: 斷言

給一個例子(c#)

[TestMethod] public void Withdraw_ValidAmount_ChangesBalance() { // arrange double currentBalance = 10.0; double withdrawal = 1.0; double expected = 9.0; var account = new CheckingAccount("JohnDoe", currentBalance); // act account.Withdraw(withdrawal); double actual = account.Balance; // assert Assert.AreEqual(expected, actual); } 

服務間的接口測試用例

服務間的接口測試實際上是黑盒測試,3A原則也適用於這種測試用例的編寫

  • A: arrange 初始化測試數據,就是造數據,這里的數據有我們輸入的數據,也有目標接口所涉及的資源,比如hr系統中的用戶信息,我們必須先有幾條人員的詳細信息才能去測獲取人員信息的接口(當然只是正常的流程,我們有時候還需要清掉數據以便測試資源為空的情況);
  • A: act 調用接口,傳入輸入數據;
  • A: assert 斷言, 對返回的資源信息進行斷言,比如獲取用戶信息的接口返回了用戶信息之后,我們要判斷返回的用戶是不是我們想要的那個用戶,我們獲取的是李雷的信息,接口如果返回韓梅梅,那么接口的邏輯就是不對的;

舉個例子(python)

def test_get_task_by_id(self): # arrange create_task_res = self.create_task('test', 'desc') new_id = create_task_res['id'] # act url_for_get_by_id = self.ip + '/api/tasks/' + str(new_id) res = requests.request("GET", url_for_get_by_id).json() # assert self.assertEqual(res['id'], new_id) 

手工測試用例

手工的功能測試用例也可以用3A原則來編寫。

  • Arrange: 准備被測功能相關的測試數據,比如往系統里錄入一批工單以便測試工單的分頁功能
  • Act : 調用被測的功能,實際上這就是我們一直講的測試步驟
  • Assert: 斷言

舉個例子

# arrange and act 打開chrome瀏覽器並跳轉至http://localhost/wordpress/wp-login.php 在用戶名文本框中輸入admin 在密碼文本框中輸入admin 點擊登陸按鈕 # assert 瀏覽器跳轉到http://localhost/wordpress/wp-admin/ 右上角出現“你好,admin”字樣 

總結

總之對於接口的自動化測試用例說來,遵循3A原則就意味着

  • Arrange: 測試用例執行之前需要准備測試數據,包括需要輸入的數據及存量數據
  • Act: 通過不同的參數來調用接口,並拿到返回
  • Assert: 必須做斷言,否則用例就沒有任何意義了

 

6.unittest框架

 

在我們真正的編寫測試用例之前,我們需要了解一下測試框架。

unittest是python自帶的單元測試框架,盡管其主要是為單元測試服務的,但我們也可以用它來做接口的自動化測試。

unittest框架為我們編寫用例提供了如下的能力

  • 定義用例的能力。unittest框架有一套固有套路,可以讓我們定義測試用例時更加簡單和統一

  • 斷言的能力。unittest框架提供了一系列的斷言

  • 各種執行策略。通過test suit或者擴展的方式,我們可以自定義用例執行的策略

簡單的例子

import unittest class StringTestCase(unittest.TestCase): def setUp(self): # Arrange self.test_string = "This is a string" def testUpper(self): # Act and Assert self.assertEqual("THIS IS A STRING", self.test_string.upper()) if __name__ == '__main__': unittest.main() 

剖析

import unittest 

導入unittest庫,不導入就沒辦法使用,好比手機如果要使用某個app就必須先安裝該app一樣,是套路,記住就好。


class StringTestCase(unittest.TestCase): 

定義測試類,初學者看到這一行就害怕,其實大可不必。這還是套路,測試類的名字你可以隨意取,當然了首字母最好大寫,這樣更符合規范一些。所有的測試類都必須直接或間接的繼承自unittest.TestCase類。總之,這還是套路,記住就好。


def setUp(self): # Arrange self.test_string = "This is a string" 

繼續套路。setUp(self)方法是一個鈎子方法,在每個測試用例執行之前都會執行一次,是做數據初始化的好地方。

在上面的例子里,我們為每一個測試方法都定義了被測對象,self.test_string


def testUpper(self): # Act and Assert self.assertEqual("THIS IS A STRING", self.test_string.upper()) 

套路繼續。這里定義了一個名為testUpper的測試方法,這個方法就是一個測試用例。

注意,只有方法名以test開頭的方法才是測試用例

self.assertEqual是一個斷言方法,作用是如果第一個參數跟第二個參數相等,那么用例通過,否則用例失敗,並在測試報告中打印出錯誤原因。上面的例子里,我們判斷self.test_string.upper()方法會將"This is a string"字符串轉換成"THIS IS A STRING"


if __name__ == '__main__': unittest.main() 

最后依然是套路,上面的代碼表示,如果直接執行該python文件的話,就運行所有的測試類里的測試用例,也就是運行所有的以test開頭的方法。

總結

使用unittest的話需要記住下面的幾點

  • 導入unittest
  • 定義繼承自unittest.TestCase的測試類
  • 定義以test開頭的測試方法,這個方法就是測試用例,你可以在一個類里定義n個測試用例
  • 斷言
  • unittest.main()是執行測試用例最簡單的方式

7.requests庫

requests庫可以極大的簡化我們發送http請求及獲取響應的代碼,簡潔而優雅。

簡單示例

>>> import requests >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) >>> r.status_code 200 >>> r.headers['content-type'] 'application/json; charset=utf8' >>> r.encoding 'utf-8' >>> r.text u'{"type":"User"...' >>> r.json() {u'private_gists': 419, u'total_private_repos': 77, ...} 

上面的例子相信大家很容易看明白,在我們做接口自動化測試的時候,我們往往使用requests的提供的接口發送請求和獲取響應,並根據響應類型將響應轉換成python自建的數據結構。比如上面的例子里,我們將響應的json字符串轉換成了python的dict。

安裝

官方文檔 :參考http://docs.python-requests.org/en/master/user/install/

快速開始

官方文檔 :參考http://docs.python-requests.org/en/master/user/quickstart/

划重點

 

 

8.第一個用例

 

前面鋪墊了很多的基礎知識,掌握基礎知識是做基於http接口自動化測試的前提,不建議直接跳過。

前提條件

學習本節需要有一些前提條件

  • 安裝了postman
  • 安裝了python
  • 安裝了requests

用例描述

認識測試對象這一節里有過描述。


獲得指定節點的名字,簡介,URL 及頭像圖片的地址。

https://www.v2ex.com/api/nodes/show.json Method: GET Authentication: None 接受參數: name: 節點名(V2EX 的節點名全是半角英文或者數字) 例如: https://www.v2ex.com/api/nodes/show.json?name=python # 響應 { "id" : 90, "name" : "python", "url" : "http://www.v2ex.com/go/python", "title" : "Python", "title_alternative" : "Python", "topics" : 7669, "stars" : 4870, "header" : "這里討論各種 Python 語言編程話題,也包括 Django,Tornado 等框架的討論。這里是一個能夠幫助你解決實際問題的地方。", "footer" : null, "created" : 1278683336, "avatar_mini" : "//v2ex.assets.uxengine.net/navatar/8613/985e/90_mini.png?m=1504279401", "avatar_normal" : "//v2ex.assets.uxengine.net/navatar/8613/985e/90_normal.png?m=1504279401", "avatar_large" : "//v2ex.assets.uxengine.net/navatar/8613/985e/90_large.png?m=1504279401" } 

使用postman調試接口

在寫用例之前,我們先在postman里把接口調通,大家可以參考之前這篇

然后選擇右上角的Code菜單,如下圖所示

選擇導出為python requests的代碼,拷貝到系統剪切板,如下圖所示

導出的代碼應該是這個樣子的

import requests url = "https://www.v2ex.com/api/nodes/show.json" querystring = {"name":"python"} headers = { 'cache-control': "no-cache", 'postman-token': "a596dcc5-ab8b-8456-79c7-94a6ac11378e" } response = requests.request("GET", url, headers=headers, params=querystring) print(response.text) 

使用unittest重構代碼

導出的代碼只是3A里的Arrange和Act,我們使用unittest來重構代碼

新建文件v2ex_api_case.py

import requests import unittest class V2exAPITestCase(unittest.TestCase): def test_node_api(self): url = "https://www.v2ex.com/api/nodes/show.json" querystring = {"name":"python"} response = requests.request("GET", url, params=querystring).json() self.assertEqual(response['name'], 'python') self.assertEqual(response['id'], 90) if __name__ == '__main__': unittest.main() 

運行用例

使用下面的命令可以運行用例

python v2ex_api_case.py

運行結果

.
---------------------------------------
Ran 1 test in 0.437s OK 

總結

  • postman可以幫助我們完成50%左右的工作,比如調試接口,導出部分代碼等
  • 使用unittest重構用例可以幫助我們添加斷言,提供在命令行執行的能力,很容易跟ci工具進行集成

 

9.什么是mock server

 

使用場景

前端客戶端團隊和后端服務端團隊往往節奏是不一致的。前端很多情況下需要等待后台的api開發完成后才能進行開發聯調和測試,這種前后端不對稱就造成了前后端團隊節奏不一致,從而造成整個項目/產品交付/發布延期。

有一種解決方案的思路是前后端先約定好后端提供的api接口的細節,前端人員自行先模擬出這些后端的實現,當然這些實現是假的,不過前端可以去調用這些假的實現,而且能拿到返回,這樣一來前端就不需要等待后端開發完成才開始工作了。

但是這樣還是會有問題,前端實現的假的api沒辦法迅速反映出后端的變化。簡單來說就是后端可能在約定好的api接口上進行了些許修改,而沒有知會前端人員,這樣前端的假的api實現並沒有相應更新,在正式聯調時就會出現問題。

像這種假的api實現,不管是前端實現的還是后端去實現的,我們可以稱之為mock server。

  • mock表示這個api返回的數據是假的,僅作為測試用的
  • server表示需要啟動服務,說到底這是一個服務程序

契約測試

由於前后端往往有一些信息不對稱,導致約定的api可能在前后端都會發生變化,所以保證前后端的一致性就成了一個挑戰。

這時候有人提出了契約測試,大致思想是前后端共用一份契約,約定了api的細節,前后端的任何變化都需要先修改契約,然后通過契約去通知前后端團隊,統一更新實現。這也是契約精神的表現。

如果為契約測試設置一種測試工具的話,我會規划下面一些特性

  • 契約的描述工具:也就是契約長什么樣子,用什么工具去定義才能讓前后端團隊秒懂

  • 通過契約自動生成mock server實現,這樣前端團隊就可以拿來即用了,如果契約修改了,那么前端團隊也很容易感知到

  • 通過契約自動生成接口測試用例,這樣通過持續運行這些接口測試用例,后端團隊就可以第一時間發現契約的修改

 

10.使用flask實現mock server

 

flask

flask是python實現的簡單的web框架,與django互補。

flask教程

如何理解flask

  • 路由 -> /request/uri

  • handler -> 路由進來之后處理request並返回response的邏輯

最簡單的例子

from flask import Flask app = Flask(__name__) @app.route("/") # 路由 def hello(): # handler return "Hello World!" 

實現mocked smile task api

獲取所有的任務

GET /api/tasks # get all tasks 

查看一個任務的詳情

GET /api/tastks/:task_id # get a task with task_id 

完成一個任務

PUT /api/tastks/:task_id # complete a task 

代碼


from flask import Flask, jsonify, g import copy app = Flask(__name__) @app.before_request def set_up_data(): g.data = [ {'id': 1, 'title': 'task 1', 'desc': 'this is task 1'}, {'id': 2, 'title': 'task 2', 'desc': 'this is task 2'}, {'id': 3, 'title': 'task 3', 'desc': 'this is task 3'}, {'id': 4, 'title': 'task 4', 'desc': 'this is task 4'}, {'id': 5, 'title': 'task 5', 'desc': 'this is task 5'} ] g.task_does_not_exist = {"msg": "task does not exist"} @app.route('/api/tasks') def get_all_tasks(): return jsonify(g.data) @app.route('/api/tasks/<int:task_id>') def get_task(task_id): if task_id > 0 and task_id <= len(g.data): return jsonify(g.data[task_id]) else: return jsonify(g.task_does_not_exist) @app.route('/api/tasks/<int:task_id>', methods=['PUT']) def complete_task(task_id): if task_id > 0 and task_id <= len(g.data): tmp = copy.deepcopy(g.data[task_id]) tmp['done'] = True return jsonify(tmp) else: return jsonify(g.task_does_not_exist) 

運行

set FLASK_APP=smile_task_mock_server.py flask run * Serving Flask app "smile_task_mock_server" * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 

瀏覽器打開localhost:5000就好了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM