大廠流出,接口測試 Seldom 2.0 - 讓接口自動(dòng)化測試更簡單

HTTP 接口測試很簡單,不管工具、框架、還是平臺(tái),只要很的好的幾個(gè)點(diǎn)就是好工具。

測試數(shù)據(jù)問題:比如刪除接口,重復(fù)執(zhí)行還能保持結(jié)果一致,必定要做數(shù)據(jù)初始化。

接口依賴問題:B 接口依賴 A 的返回值,C 接口依賴 B 接口的返回值。

加密問題:不同的接口加密規(guī)則不一樣。有些用到時(shí)間戳、md5、base64、AES,如何提供種能力。

斷言問題:有些接口返回的結(jié)構(gòu)體很復(fù)雜,如何靈活的做到斷言。

對于以上問題,工具和平臺(tái)要么不支持,要么很麻煩,然而框架是最靈活的。

unittest/pytest + requests/https 直接上手寫代碼就好了,既簡單又靈活。

那么同樣是寫代碼,A 框架需要 10 行,B 框架只需要 5 行,然而又不失靈活性,那我當(dāng)然是選擇更少的了,畢竟,人生苦短嘛。

seldom 適合個(gè)人接口自動(dòng)化項(xiàng)目,它有以下優(yōu)勢。

可以寫更少的代碼

自動(dòng)生成 HTML/XML 測試報(bào)告

支持參數(shù)化,減少重復(fù)的代碼

支持生成隨機(jī)數(shù)據(jù)

支持 har 文件轉(zhuǎn) case

支持?jǐn)?shù)據(jù)庫操作

這些是 seldom 支持的功能,我們只需要集成 HTTP 接口庫,并提供強(qiáng)大的斷言即可。seldom 2.0 加入了 HTTP 接口自動(dòng)化測試支持。

Seldom 兼容 Requests API 如下:

seldom

requests

self.get()

requests.get()

self.post()

requests.post()

self.put()

requests.put()

self.delete()

requests.delete()

Seldom VS Request+unittest

先來看看 unittest + requests 是如何來做接口自動(dòng)化的:

import unittest

import requests

class TestAPI(unittest.TestCase):

? ? def test_get_method(self):

? ? ? ? payload = {'key1': 'value1', 'key2': 'value2'}

? ? ? ? r = requests.get("http://httpbin.org/get", params=payload)

? ? ? ? self.assertEqual(r.status_code, 200)

if __name__ == '__main__':

? ? unittest.main()

這其實(shí)已經(jīng)非常簡潔了。同樣的用例,用 seldom 實(shí)現(xiàn)。

# test_req.py

import seldom

class TestAPI(seldom.TestCase):

? ? def test_get_method(self):

? ? ? ? payload = {'key1': 'value1', 'key2': 'value2'}

? ? ? ? self.get("http://httpbin.org/get", params=payload)

? ? ? ? self.assertStatusCode(200)

if __name__ == '__main__':

? ? seldom.main()

主要簡化點(diǎn)在,接口的返回?cái)?shù)據(jù)的處理。當(dāng)然,seldom 真正的優(yōu)勢在斷言、日志和報(bào)告。

har to case

對于不熟悉 Requests 庫的人來說,通過 Seldom 來寫接口測試用例還是會(huì)有一點(diǎn)難度。于是,seldom 提供了har 文件轉(zhuǎn) case 的命令。

首先,打開 fiddler 工具進(jìn)行抓包,選中某一個(gè)請求。

然后,選擇菜單欄:file -> Export Sessions -> Selected Sessions...

選擇導(dǎo)出的文件格式。

點(diǎn)擊next 保存為demo.har 文件。

最后,通過seldom -h2c 轉(zhuǎn)為demo.py 腳本文件。

> seldom -h2c .\demo.har

.\demo.py

2021-06-14 18:05:50 [INFO] Start to generate testcase.

2021-06-14 18:05:50 [INFO] created file: D:\.\demo.py

demo.py 文件。

import seldom

class TestRequest(seldom.TestCase):

? ? def start(self):

? ? ? ? self.url = "http://httpbin.org/post"

? ? def test_case(self):

? ? ? ? headers = {"User-Agent": "python-requests/2.25.0", "Accept-Encoding": "gzip, deflate", "Accept": "application/json", "Connection": "keep-alive", "Host": "httpbin.org", "Content-Length": "36", "Origin": "http://httpbin.org", "Content-Type": "application/json", "Cookie": "lang=zh"}

? ? ? ? cookies = {"lang": "zh"}

? ? ? ? self.post(self.url, json={"key1": "value1", "key2": "value2"}, headers=headers, cookies=cookies)

? ? ? ? self.assertStatusCode(200)

if __name__ == '__main__':

? ? seldom.main()

運(yùn)行測試

打開 debug 模式seldom.run(debug=True) 運(yùn)行上面的用例。

> python .\test_req.py

2021-04-29 18:19:39 [INFO] A run the test in debug mode without generating HTML report!

2021-04-29 18:19:39 [INFO]

? ? ? ? ? ? ? __? ? __

? ________? / /___/ /___? ____ ____

? / ___/ _ \/ / __? / __ \/ __ ` ___/

(__? )? __/ / /_/ / /_/ / / / / / /

/____/\___/_/\__,_/\____/_/ /_/ /_/

-----------------------------------------

? ? ? ? ? ? ? ? ? ? ? ? ? ? @itest.info

test_get_method (test_req.TestAPI) ...

----------- Request ?? ---------------

url: http://httpbin.org/get? ? ? ? method: GET

----------- Response ??? -------------

type: json

{'args': {'key1': 'value1', 'key2': 'value2'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.22.0', 'X-Amzn-Trace-Id': 'Root=1-608a883c-7b355ba81fcd0d287566405a'}, 'origin': '183.178.27.36', 'url': 'http://httpbin.org/get?key1=value1&key2=value2'}

ok

----------------------------------------------------------------------

Ran 1 test in 0.619s

OK

通過日志/報(bào)告都可以清楚的看到。

請求的方法

請求 url

響應(yīng)的類型

響應(yīng)的數(shù)據(jù)

更強(qiáng)大的斷言

斷言接口返回的數(shù)據(jù)是我們在做接口自動(dòng)化很重要的工作。

assertJSON

接口返回結(jié)果如下:

{

? "args":{

? ? "hobby":[

? ? ? "basketball",

? ? ? "swim"

? ? ],

? ? "name":"tom"

? }}

我的目標(biāo)是斷言name 和 hobby 部分的內(nèi)容。seldom 可以針對JSON文件進(jìn)行斷言。

import seldom

class TestAPI(seldom.TestCase):

? ? def test_assert_json(self):

? ? ? ? payload = {'name': 'tom', 'hobby': ['basketball', 'swim']}

? ? ? ? self.get("http://httpbin.org/get", params=payload)

? ? ? ? assert_json = {'args': {'hobby': ['swim', 'basketball'], 'name': 'tom'}}

? ? ? ? self.assertJSON(assert_json)

運(yùn)行日志

test_get_method (test_req.TestAPI) ...

----------- Request ?? ---------------

url: http://httpbin.org/get? ? ? ? method: GET

----------- Response ??? -------------

type: json

{'args': {'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.22.0', 'X-Amzn-Trace-Id': 'Root=1-608a896d-48fac4f6139912ba01d2626f'}, 'origin': '183.178.27.36', 'url': 'http://httpbin.org/get?name=tom&hobby=basketball&hobby=swim'}

?? Assert data has not key: headers

?? Assert data has not key: origin

?? Assert data has not key: url

ok

----------------------------------------------------------------------

Ran 1 test in 1.305s

OK

seldom 還會(huì)提示你還有哪些字段沒有斷言。

assertPath

接口返回?cái)?shù)據(jù)如下:

{

? "args":{

? ? "hobby":

? ? ? ["basketball","swim"],

? ? "name":"tom"

? }}

seldom 中可以通過 path 進(jìn)行斷言:

import seldom

class TestAPI(seldom.TestCase):

? ? def test_assert_path(self):

? ? ? ? payload = {'name': 'tom', 'hobby': ['basketball', 'swim']}

? ? ? ? self.get("http://httpbin.org/get", params=payload)

? ? ? ? self.assertPath("name", "tom")

? ? ? ? self.assertPath("args.hobby[0]", "basketball")

assertSchema

有時(shí)并不關(guān)心數(shù)據(jù)本身是什么,而是需要斷言數(shù)據(jù)的類型。 assertSchema 是基于 jsonschema 實(shí)現(xiàn)的斷言方法。

jsonschema: https://json-schema.org/learn/

接口返回?cái)?shù)據(jù)如下:

{

? "args":{

? ? "hobby":

? ? ? ["basketball","swim"],

? ? "name":"tom",

? ? "age":"18"

? }}

seldom 中可以通過利用jsonschema 進(jìn)行斷言:

import seldom

class TestAPI(seldom.TestCase):

? ? def test_assert_schema(self):

? ? ? ? payload = {"hobby": ["basketball", "swim"], "name": "tom", "age": "18"}

? ? ? ? self.get("/get", params=payload)

? ? ? ? schema = {

? ? ? ? ? ? "type": "object",

? ? ? ? ? ? "properties": {

? ? ? ? ? ? ? ? "args": {

? ? ? ? ? ? ? ? ? ? "type": "object",

? ? ? ? ? ? ? ? ? ? "properties": {

? ? ? ? ? ? ? ? ? ? ? ? "age": {"type": "string"},

? ? ? ? ? ? ? ? ? ? ? ? "name": {"type": "string"},

? ? ? ? ? ? ? ? ? ? ? ? "hobby": {

? ? ? ? ? ? ? ? ? ? ? ? ? ? "type": "array",

? ? ? ? ? ? ? ? ? ? ? ? ? ? "items": {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "type": "string"

? ? ? ? ? ? ? ? ? ? ? ? ? ? },

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? },

? ? ? ? }

? ? ? ? self.assertSchema(schema)

是否再次感受到了 seldom 提供的斷言非常靈活,強(qiáng)大。

接口數(shù)據(jù)依賴

在場景測試中,我們需要利用上一個(gè)接口的數(shù)據(jù),調(diào)用下一個(gè)接口。

import seldom

class TestRespData(seldom.TestCase):

? ? def test_data_dependency(self):

? ? ? ? """

? ? ? ? Test for interface data dependencies

? ? ? ? """

? ? ? ? headers = {"X-Account-Fullname": "bugmaster"}

? ? ? ? self.get("/get", headers=headers)

? ? ? ? self.assertStatusCode(200)

? ? ? ? username = self.response["headers"]["X-Account-Fullname"]

? ? ? ? self.post("/post", data={'username': username})

? ? ? ? self.assertStatusCode(200)

seldom 提供了self.response用于記錄上個(gè)接口返回的結(jié)果,直接拿來用即可。

數(shù)據(jù)驅(qū)動(dòng)

seldom 本來就提供的有強(qiáng)大的數(shù)據(jù)驅(qū)動(dòng),拿來做接口測試非常方便。

@data

import seldom

from seldom import data

class TestDDT(seldom.TestCase):

? ? @data([

? ? ? ? ("key1", 'value1'),

? ? ? ? ("key2", 'value2'),

? ? ? ? ("key3", 'value3')

? ? ])

? ? def test_data(self, key, value):

? ? ? ? """

? ? ? ? Data-Driver Tests

? ? ? ? """

? ? ? ? payload = {key: value}

? ? ? ? self.post("/post", data=payload)

? ? ? ? self.assertStatusCode(200)

? ? ? ? self.assertEqual(self.response["form"][key], value)

@file_data

創(chuàng)建data.json數(shù)據(jù)文件

{

"login":[

? ? ["admin","admin123"],

? ? ["guest","guest123"]

]}

通過file_data實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)。

import seldom

from seldom import file_data

class TestDDT(seldom.TestCase):

? ? @file_data("data.json", key="login")

? ? def test_data(self, username, password):

? ? ? ? """

? ? ? ? Data-Driver Tests

? ? ? ? """

? ? ? ? payload = {username: password}

? ? ? ? self.post("http://httpbin.org/post", data=payload)

? ? ? ? self.assertStatusCode(200)

? ? ? ? self.assertEqual(self.response["form"][username], password)

更過數(shù)據(jù)文件 (csv/excel/yaml),參考

隨機(jī)生成測試數(shù)據(jù)

seldom 提供隨機(jī)生成測試數(shù)據(jù)方法,可以生成一些常用的數(shù)據(jù)。

import seldom

from seldom import testdata

class TestAPI(seldom.TestCase):

? ? def test_data(self):

? ? ? ? phone = testdata.get_phone()

? ? ? ? payload = {'phone': phone}

? ? ? ? self.get("http://httpbin.org/get", params=payload)

? ? ? ? self.assertPath("args.phone", phone)

更過類型的測試數(shù)據(jù),參考

數(shù)據(jù)庫操作

seldom 支持 sqlite3、MySQL 數(shù)據(jù)庫操作。

sqlite3

MySQL

delete_data()

delete_data()

insert_data()

insert_data()

select_data()

select_data()

update_data()

update_data()

init_table()

init_table()

close()

close()

連接數(shù)據(jù)庫

連接 sqlit3 數(shù)據(jù)庫

from seldom.db_operation import SQLiteDB

db = SQLiteDB(r"D:\learnAPI\db.sqlite3")

連接 MySQL 數(shù)據(jù)庫(需要)

安裝 pymysql 驅(qū)動(dòng)

> pip install pymysql

鏈接

from seldom.db_operation import MySQLDB

db = MySQLDB(host="127.0.0.1",

? ? ? ? ? ? port="3306",

? ? ? ? ? ? user="root",

? ? ? ? ? ? password="123",

? ? ? ? ? ? database="db_name")

操作方法

delete_data

刪除表數(shù)據(jù)。

db.delete_data(table="user", where={"id":1})

insert_data

插入一條數(shù)據(jù)。

data = {'id': 1, 'username': 'admin', 'password': "123"},

db.insert_data(table="user", data=data)

select_data

查詢表數(shù)據(jù)。

result = db.select_data(table="user", where={"id":1, "name": "tom"})

print(result)

update_data

更新表數(shù)據(jù)。

db.update_data(table="user", data={"name":"new tom"}, where={"name": "tom"})

init_table

批量插入數(shù)據(jù),在插入之前先清空表數(shù)據(jù)。

datas = {

? ? 'api_event': [

? ? ? ? {'id': 1, 'name': '紅米Pro發(fā)布會(huì)'},

? ? ? ? {'id': 2, 'name': '可參加人數(shù)為0'},

? ? ? ? {'id': 3, 'name': '當(dāng)前狀態(tài)為0關(guān)閉'},

? ? ? ? {'id': 4, 'name': '發(fā)布會(huì)已結(jié)束'},

? ? ? ? {'id': 5, 'name': '小米5發(fā)布會(huì)'},

? ? ],

? ? 'api_guest': [

? ? ? ? {'id': 1, 'real_name': 'alen'},

? ? ? ? {'id': 2, 'real_name': 'has sign'},

? ? ? ? {'id': 3, 'real_name': 'tom'},

? ? ]

}

db.init_table(datas)

close

關(guān)閉數(shù)據(jù)庫連接。

db.close()

最后,基于 seldom 實(shí)現(xiàn)接口自動(dòng)化測試的項(xiàng)目:https://github.com/defnngj/pyrequest2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容