1. 程式人生 > 其它 >用 Python 為介面測試自動生成用例

用 Python 為介面測試自動生成用例

用Python為介面自動生成測試用例

基於屬性的測試會產生大量的、隨機的引數,特別適合為單元測試和介面測試生成測試用例

儘管早在2006年haskell語言就有了QuickCheck來進行”基於屬性的測試“,但是目前來看這依然是一個比較小眾的領域,參考資料有限,本文如有不足,歡迎指正。


1. 基於表的測試

在過去的測試實踐中,執行測試時通常需要明確的內容(Value):

  1. 條件
  2. 輸入
  3. 結果

這些內容可以通過”判定樹“或者”判斷表“來表示,然後測試的執行過程變成了這樣

“給定輸入 X,我們期望 Y”

可以稱為 基於表的測試

在最初,這給了我們測試的方向,但是缺點也非常明顯:

你要足夠多的"X->Y" 才能可能覆蓋到隱蔽的bug。

這裡請大家回答幾個問題:

  1. 你是否已經為每一個測試編寫了足夠多的用例,以至於你十分確定真的不需要再增加用例了
  2. 這麼多的用例,你編寫的是否開心?是否高效?是否願意繼續堅持?

如果以上問題的答案不是yes,那麼基於屬性的測試就是你需要掌握的東西!


2. 基於屬性的測試

基於屬性的測試和基於表的測試,最大的區別可以這樣描述:

“給定輸入 X值,我們期望 Y值”

vs

“給定輸入 X類,我們期望 Y類”

於是利用工具生成大量的X類資料,進行測試,並驗證結果是否Y類。

值得注意的是:

  1. 關注輸入的型別,而不是輸入的值
  2. 根據型別自動生成大量的、隨機的輸入值

換言之,它在自動的生成測試用例

,雖然輸入值是隨機的,但是值得型別符合規範要求


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 = "http://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

原創不易,歡迎關注,不錯過技術乾貨,謝謝鼓勵!