用 Python 為介面測試自動生成用例
用Python為介面自動生成測試用例
基於屬性的測試會產生大量的、隨機的參數,特別適合為單元測試和介面測試生成測試用例
儘管早在2006年haskell語言就有了QuickCheck
來進行」基於屬性的測試「,但是目前來看這依然是一個比較小眾的領域,參考資料有限,本文如有不足,歡迎指正。
1. 基於表的測試
在過去的測試實踐中,執行測試時通常需要明確的內容(Value):
- 條件
- 輸入
- 結果
這些內容可以通過」判定樹「或者」判斷表「來表示,然後測試的執行過程變成了這樣
「給定輸入 X,我們期望 Y」
可以稱為 基於表的測試
在最初,這給了我們測試的方向,但是缺點也非常明顯:
你要足夠多的”X->Y” 才能可能覆蓋到隱蔽的bug。
這裡請大家回答幾個問題:
- 你是否已經為每一個測試編寫了足夠多的用例,以至於你十分確定真的不需要再增加用例了
- 這麼多的用例,你編寫的是否開心?是否高效?是否願意繼續堅持?
如果以上問題的答案不是yes,那麼基於屬性的測試就是你需要掌握的東西!
2. 基於屬性的測試
基於屬性的測試和基於表的測試,最大的區別可以這樣描述:
「給定輸入 X值,我們期望 Y值」
vs
「給定輸入 X類,我們期望 Y類」
於是利用工具生成大量的X類數據,進行測試,並驗證結果是否Y類。
值得注意的是:
- 關注輸入的類型,而不是輸入的值
- 根據類型自動生成大量的、隨機的輸入值
換言之,它在自動的生成測試用例,雖然輸入值是隨機的,但是值得類型符合規範要求
3. 如何進行基於屬性的測試
在不同的語言中有不同的工具來實現,比如:
- haskell中的
QuickCheck
、 - java中的
quicktheories
- python中的
hypothesis
本文以python為例進行演示:
假設有add函數,接收兩個類型整數參數,並返回它們的相加結果
def add(a: int, b: int) -> int:
pass
首先寫出一個簡單的測試用例
def test_add():
assert 3 == add(1, 2)
正如前面所說,一個這樣的用例,根本沒信心覆蓋全部的場景,例如:
- 參數是0
- 參數是負數
- 參數值特別大
- 其他…
所以接下來怎麼辦?
改為基於屬性的測試
import hypothesis.strategies as st
from hypothesis import given
@given(st.integers(), st.integers())
def test_add(a, b):
c = add(a, b)
print(f"{a=},{b=}, {c=}")
assert isinstance(c, int)
assert c == a + b
執行結果
a=0,b=0, c=0
a=0,b=0, c=0
a=0,b=0, c=0
a=16926,b=11053, c=27979
a=0,b=0, c=0
a=0,b=0, c=0
a=21010,b=-2732672789497425072, c=-2732672789497404062
a=0,b=0, c=0
a=14554,b=-15956, c=-1402
a=15597,b=0, c=15597
a=15597,b=13, c=15610
a=15597,b=0, c=15597
a=15597,b=15597, c=31194
a=28965,b=-36, c=28929
a=113,b=-36, c=77
a=28929,b=-36, c=28893
a=28929,b=9356, c=38285
a=9356,b=9356, c=18712
a=10278,b=-62, c=10216
a=-62,b=-62, c=-124
a=1625,b=-68244995710046113596363052355575247332, c=-68244995710046113596363052355575245707
a=1625,b=-13143, c=-11518
a=1625,b=1625, c=3250
a=-1940,b=-31868, c=-33808
a=-7,b=1, c=-6
a=-1,b=0, c=-1
a=-1,b=0, c=-1
a=13,b=7245, c=7258
a=13,b=13, c=26
a=-99,b=-18, c=-117
a=-30172,b=66, c=-30106
a=-30172,b=-16940, c=-47112
a=-16940,b=-16940, c=-33880
a=-1233214851,b=-5152, c=-1233220003
a=-5152,b=-5152, c=-10304
a=-16,b=-29706, c=-29722
a=-29706,b=-29706, c=-59412
a=-29706,b=116, c=-29590
a=29696,b=90, c=29786
a=29696,b=29696, c=59392
a=-11446,b=-21185, c=-32631
a=-21185,b=-21185, c=-42370
a=-12,b=16437, c=16425
a=-12,b=16437, c=16425
a=3202,b=53, c=3255
a=3202,b=3202, c=6404
a=-98,b=3, c=-95
a=82691970030325711417874227410289695610,b=1316378701, c=82691970030325711417874227411606074311
a=82691970030325711417874227410289695610,b=82691970030325711417874227410289695610, c=165383940060651422835748454820579391220
a=24100,b=14385, c=38485
a=24100,b=14385, c=38485
a=24100,b=24100, c=48200
a=12293,b=-106, c=12187
a=12293,b=27280, c=39573
a=27280,b=27280, c=54560
a=-18887,b=-5530, c=-24417
a=-18887,b=-18887, c=-37774
a=4738,b=122351151658095310625663643505383743930, c=122351151658095310625663643505383748668
a=-24601163521689169516616964879873921492,b=565838202, c=-24601163521689169516616964879308083290
a=4738,b=122351151658095310625663643281738736058, c=122351151658095310625663643281738740796
a=-96098295006598318424285019062007505,b=25207226, c=-96098295006598318424285019036800279
a=-96098295006598318424285019062007505,b=-1997122225172868107163535967078611096, c=-2093220520179466425587820986140618601
a=-96098295006598318424285019062007505,b=-96098295006598318424285019062007505, c=-192196590013196636848570038124015010
a=-36,b=1190, c=1154
a=-36,b=-4, c=-40
a=-36,b=-36, c=-72
a=2047897602,b=-4641, c=2047892961
a=2047897602,b=2047897602, c=4095795204
a=-1307873608,b=11753, c=-1307861855
a=-1307873608,b=-3308225400997338452, c=-3308225402305212060
a=-1307873608,b=770256249, c=-537617359
a=-1307873608,b=-1307873608, c=-2615747216
a=-4715910568460396013,b=-18622, c=-4715910568460414635
a=16754,b=-6053, c=10701
a=-6053,b=-6053, c=-12106
a=-6053,b=-6053, c=-12106
a=-22264,b=44, c=-22220
a=-22264,b=-22264, c=-44528
a=-86,b=-86, c=-172
a=-86,b=-86, c=-172
a=1794,b=28170, c=29964
a=1794,b=28170, c=29964
a=-93,b=482, c=389
a=-1,b=482, c=481
a=-1,b=-1, c=-2
由結果可知,工具根據參數是整數這一規範,自動生成、執行了大量的測試用例
4. 在介面測試中自動生成用例
介面測試和函數的單元測試非常相似:
- 輸入數據
- 返回數據
此外介面文檔作為前後端、甚至測試開發的對接窗口,對參數的要求約定的更加細緻,
以OpenAPI為例,每個參數可以有以下屬性:
- type:數字還是字元串?
- format:密碼還是電子郵箱?
- maxLength:長度不超過多少?
- required:是否必填?
- in:參數通過什麼傳遞?
- 其他…
於是為介面生成符合要求的參數就變得可行了,舉個例子:
import logging
import unittest
import requests
from api_tools import APITestCase
class UserCase(APITestCase):
schema = "//127.0.0.1:7600/openapi.json"
if __name__ == "__main__":
unittest.main()
這是以unittest為例進行封裝的結果,只需要在TestCase中指定openapi的內容(或路徑),
啟動測試框架時,會自動讀取、解析介面文檔,並生成測試用例
下面是部分執行日誌,可以看到對介面發送了隨機參數,並獲得返回值
INFO 2022-04-10 01:02:59,223 : 執行用例 login_login_access_token_post
INFO 2022-04-10 01:02:59,223 : -----調用介面:login_login_access_token_post-----
INFO 2022-04-10 01:02:59,223 : 發送請求>>> :請求數據 = {'userin': {'password': 'bEYtwDZUxdBVThEFOqTz', 'email': 'KEfQnAtTNtwZOXRjoIjp'}}
INFO 2022-04-10 01:02:59,578 : 接收響應 <<<: <Response [400]>
INFO 2022-04-10 01:02:59,578 : -----調用完畢:login_login_access_token_post-----
INFO 2022-04-10 01:02:59,578 : 執行用例 test_token_login_test_token_post
INFO 2022-04-10 01:02:59,578 : -----調用介面:test_token_login_test_token_post-----
INFO 2022-04-10 01:02:59,578 : 發送請求>>> :請求數據 = {}
INFO 2022-04-10 01:03:00,035 : 接收響應 <<<: <Response [200]>
INFO 2022-04-10 01:03:00,035 : -----調用完畢:test_token_login_test_token_post-----
INFO 2022-04-10 01:03:00,035 : 執行用例 sign_up_login_sign_up_post
INFO 2022-04-10 01:03:00,035 : -----調用介面:sign_up_login_sign_up_post-----
INFO 2022-04-10 01:03:00,035 : 發送請求>>> :請求數據 = {'userin': {'password': 'GgzYcOwapTwnkkKVTraE', 'email': 'EJrXlCSNKKjdiVvAOnTM'}}
INFO 2022-04-10 01:03:00,657 : 接收響應 <<<: <Response [200]>
INFO 2022-04-10 01:03:00,657 : -----調用完畢:sign_up_login_sign_up_post-----
INFO 2022-04-10 01:03:00,657 : 執行用例 todo_list_todo_get
INFO 2022-04-10 01:03:00,657 : -----調用介面:todo_list_todo_get-----
INFO 2022-04-10 01:03:00,657 : 發送請求>>> :請求數據 = {}
INFO 2022-04-10 01:03:01,233 : 接收響應 <<<: <Response [200]>
INFO 2022-04-10 01:03:01,233 : -----調用完畢:todo_list_todo_get-----
INFO 2022-04-10 01:03:01,233 : 執行用例 todo_post_todo_post
INFO 2022-04-10 01:03:01,233 : -----調用介面:todo_post_todo_post-----
INFO 2022-04-10 01:03:01,233 : 發送請求>>> :請求數據 = {'todoin': {'title': '', 'is_done': False}}
INFO 2022-04-10 01:03:01,594 : 接收響應 <<<: <Response [200]>
INFO 2022-04-10 01:03:01,594 : -----調用完畢:todo_post_todo_post-----
INFO 2022-04-10 01:03:01,594 : 執行用例 todo_delete_all_todo_delete
INFO 2022-04-10 01:03:01,594 : 執行用例 todo_get_todo__todo_id__get
INFO 2022-04-10 01:03:01,594 : -----調用介面:todo_get_todo__todo_id__get-----
INFO 2022-04-10 01:03:01,594 : 發送請求>>> :請求數據 = {'todo_id': 2451}
INFO 2022-04-10 01:03:02,026 : 接收響應 <<<: <Response [404]>
INFO 2022-04-10 01:03:02,026 : -----調用完畢:todo_get_todo__todo_id__get-----
INFO 2022-04-10 01:03:02,026 : 執行用例 todo_put_todo__todo_id__put
INFO 2022-04-10 01:03:02,026 : -----調用介面:todo_put_todo__todo_id__put-----
INFO 2022-04-10 01:03:02,026 : 發送請求>>> :請求數據 = {'todo_id': 1519, 'todoin': {'title': '', 'is_done': False}}
INFO 2022-04-10 01:03:02,423 : 接收響應 <<<: <Response [404]>
INFO 2022-04-10 01:03:02,423 : -----調用完畢:todo_put_todo__todo_id__put-----
INFO 2022-04-10 01:03:02,423 : 執行用例 todo_delete_todo__todo_id__delete
鑒於篇幅有限,暫時介紹這麼多,
文章首發與我的公眾號測試開發研習社,如果你對這個話題依舊有興趣,可以回復或評論:PBT
原創不易,歡迎關注,不錯過技術乾貨,謝謝鼓勵!