零基礎Python接口測試教程

1、Python基礎

大綱

Python簡介、環境搭建及包管理

Python簡介

  1. 特色:Python是一門動態、解釋型、強類型語言
    1. 動態:在運行期間才作數據檢查(不用提早聲明變量)- 靜態語音(C/Java):編譯時檢查數據類型(編碼時須要聲明變量類型)
    2. 解釋型:在執行程序時,才一條條解釋成機器語言給計算機執行(無需編譯,速度較慢)- 編譯型語言(C/Java):先要將代碼編譯成二進制可執行文件,再執行
    3. 強類型:類型安全,變量一旦被指定了數據類型,若是不強制轉換,那麼永遠是這種類型(嚴謹,避免類型錯誤,速度較慢)- 弱類型(VBScript/JavaScript): 類型在運行期間會轉化,如 js中的 1+"2"="12", 1會由數字轉化爲string
  2. 編碼原則:優雅、明確、簡單
  3. 優勢
    1. 簡單易學
    2. 開發效率高
    3. 高級語言
    4. 可移植、可擴展、可嵌入
    5. 龐大的三方庫
  4. 缺點
    1. 速度慢
    2. 代碼不能加密
    3. 多線程不能充分利用多核cpu(GIL全局解釋性鎖,同一時刻只能運行一個線程)
  5. 應用領域
    1. 自動化測試(UI/接口)
    2. 自動化運維
    3. 爬蟲
    4. Web開發(Django/Flask/..)
    5. 圖形GUI開發
    6. 遊戲腳本
    7. 金融、量化交易
    8. 數據分析,大數據
    9. 人工智能、機器學習、NLP、計算機視覺
    10. 雲計算

環境搭建

Windows Python3環境搭建前端

  1. 下載Python3.6.5*.exe安裝包
  2. 雙擊安裝,第一個節目選中Add Python3.6.5 to PATH,點擊Install Now(默認安裝pip),一路下一步
  3. 驗證:打開cmd命令行,輸入python,應能進入python shell 並顯示爲Python 3.6.5版本

包管理

  1. pip安裝
    1. pip install 包名 - 卸載: pip uninstall 包名
    2. pip install 下載的whl包.whl
    3. pip install -r requiements.txt(安裝requirements.txt中的全部依賴包)
    4. pip list 查看已安裝的三方包,pip freeze 已文件格式顯示已安裝的三方包(用於導出requiremnts.txt文件)
  2. 源碼安裝
    1. 下載源碼包,解壓,進入解壓目錄
    2. 打開命令行,執行 python setup.py install
    3. 驗證:進入python shell,輸入import 包名,不報錯表示安裝成功
  3. 三方包默認安裝路徑:Python3.6.5/Lib/site-packages/ 下

Python基本語法

  1. 縮進
if x > 0:
    print("正數")
elif x = 0:
    print("0")
else:
    print("負數")

def add(x,y):
    return x+y
  1. 一行多條語句
x=1; y=2; print(x+y)
  1. 斷行
print("this line is too long, \ 
so I break it to two lines")
  1. 註釋
# 單行註釋
a = 1

'''這是一段
多行註釋'''

def add(x, y):
    """加法函數:這是docstring(函數說明)"""
    pass
  1. 變量
    1. 變量類型(局部變量、全局變量、系統變量)
    2. 變量賦值
      • 多重賦值x=y=z=1
      • 多元賦值x,y = y,x
    3. 變量自增 x+=1 x-=1(不支持x++, x--)

Python3中沒有常量java

基本數據類型(6種)

1. 數字Number

  1. 種類
    1. 整型int(Python3中沒有長整型,int長度幾乎沒有限制)
    2. 浮點型float
    3. 布爾型bool
      • False: 0,0.0,'',,(),{}
      • True: 除False之外,['']或[,]不是False
    4. 複數型complex
  2. 操做符: +,-,*,/,//(地板除),**(乘方) - Python3中的/是真實除,1/2=0.5
  3. 類型轉
    1. str(): 其餘類型轉爲字符串, 如str(12)
    2. int(): 字符串數字轉爲整型(字符串不是純整數會報錯), 如int("12")
    3. float(): 字符串轉換爲浮點數,如float("1.23")

2. 字符串String

  1. 字符串系統方法
    • len(): 計算字符串長度,如len("abcdefg")
    • find()/index(): 查找字符串中某個字符第一次出現的索引(index()方法查找不到會報錯), 如"abcdefg".find("b"); "abcedfgg".index("g")
    • lower()/upper(): 將字符串轉換爲全小寫/大寫,如"AbcdeF".lower();"abcedF".upper()
    • isdigit()/isalpha()/isalnum(): 判斷字符串是否純數字/純字母/純數字字母組合, 如isdigit("123"),結果爲 True
    • count(): 查詢字符串中某個元素的數量,如"aabcabc".count("a")
    • join(): 將列表元素按字符串鏈接,如"".join(["a","b","c"])會按空字符鏈接列表元素,獲得"abc"
    • replace(): 替換字符串中的某已部分,如"hello,java".replace("java", "python"),將java 替換爲 python
    • split(): 和join相反,將字符串按分隔符分割成列表, 如"a,b,c,d".split(",")獲得["a", "b", "c", "d"]
    • strip()/lstrip()/rstrip(): 去掉字符串左右/左邊/右邊的無心字符(包括空格,換行等非顯示字符),如" this has blanks \n".strip()獲得"this has balnks"
  2. 字符串格式化
    • %: 如"Name: %s, Age: %d" % ("Lily", 12)"Name: %(name)s, Age: %(age)d" % {"name": "Lily", "age": 12}
    • format: 如"Name: {}, Age: {}".format("Lily", 12)"Name: {name}, Age: {age}".format(name="Lily",age=12)
    • substitude(不徹底替換會報錯)/safe_substitude: 如"Name: ${name}, Age: ${age}".safe_substitude(name="Lily",age=12)
  3. 案例: 利用format生成自定義html報告
tpl='''<html>
    <head><title>{title}</title></head>
    <body>
    <h1>{title}</h1>
    <table border=1px>
        <tr>
            <th>序號</th>
            <th>用例</th>
            <th>結果</th>
        </tr>
        {trs}
    </table>
    </body>
    </html>
    '''
    
    tr='''<tr><td>{sn}</td>
    <td>{case_name}</td>
    <td>{result}</td>
    '''
    
    title="自動化測試報告"
    case_results = [("1", "test_add_normal", "PASS"),("2", "test_add_negative", "PASS"), ("3", "test_add_float", "FAIL")]
    
    trs=''
    for case_result in case_results:
        tr_format = tr.format(sn=case_result[0], case_name=case_result[1], result=case_result[2])
        trs += tr_format
        
    html = tpl.format(title=title, trs=trs)
    
    f = open("report.html", "w")
    f.write(html)
    f.close()

結果預覽node

自動化測試報告

序號 用例 結果
1 test_add_normal PASS
2 test_add_negative PASS
3 test_add_float FAIL

3. 列表List

列表元素支持各類對象的混合,支持嵌套各類對象,如["a", 1, {"b": 3}, [1,2,3]]python

  1. 列表操做
    • 賦值: l = [1, "hello", ("a", "b")]
    • 獲取: a = l[0] # 經過索引獲取
    • 增: l.append("c");l.extend(["d","e"]);l+["f"]
    • 刪: l.pop() # 按索引刪除,無參數默認刪除最後一個;l.remove("c") # 按元素刪除
    • 改:l[1]="HELLO" # 經過索引修改
    • 查: 遍歷 for i in l: print(i)
  2. 列表系統方法
    • append()/insert()/extend(): 添加/插入/擴展(鏈接)
    • index(): 獲取元素索引
    • count(): 統計元素個數
    • pop()/remove(): 按索引/元素刪除
    • sort()/reverse(): 排序/反轉
    • 案例: 字符串反轉s="abcdefg"; r=''.join(reversed(a))

4. 元組Tuple

  1. 不可改變,經常使用做函數參數(安全性好)
  2. 一樣支持混合元素以及嵌套
  3. 只有一個元素時,必須加","號,如a=("hello",) - 由於Python中()還有分組的含義,不加","會識別爲字符串

字符串/列表/元組統稱爲序列, 有類似的結構和操做方法mysql

序列相關操做方法

  1. 索引
    • 正反索引: l[3];l[-1]
    • 索引溢出(IndexError): 當索引大於序列的最大索引時會報錯,如[1,2,3,4]最大索引是3,引用l[4]會報IndexError
  2. 切片
    • l[1:3] # 從列表索引1到索引3(不包含索引3)進行截取, 如 l = [1, 2, 3, 4, 5], l[1:3]爲[2, 3]
    • l[:5:2] # 第一個表示開始索引(留空0), 第二個表示結束索引(留空爲最後一個,即-1), 第三個是步長, 即從開頭到第5個(不包含第5個),跳一個取一個
    • 案例: 字符串反轉 s="abcdefg";r=s[::-1]
  3. 遍歷
    • 按元素遍歷: for item in l: print(item)
    • 按索引遍歷: for index in range(len(l)): print(l[index])
    • 按枚舉遍歷: for i,v in enumerate(l): print((i,v))
  4. 擴展/鏈接(添加多個元素): extend()/+ "abc"+"123";[1,2,3]+[4,5];[1,2,3].extend([4,5,6,7])
  5. 類型互轉: str()/list()/tuple()git

    list轉str通常用join(), str轉list通常用split()正則表達式

  6. 系統函數
    • len(): 計算長度
    • max()/min(): 求最大/最小元素
    • sorted()/reversed(): 排序/反轉並生成新序列(sort()/reverse()直接操做原序列)l_new=sorted(l);l_new2=reversed(l)

5. 集合Set

  1. 集合能夠經過序列生成a = set([1,2,3])
  2. 集合無序,元素不重複(全部元素爲可哈希元素)
  3. 集合分爲可變集合set和不可變集合frozenset
  4. 操做方法: 聯合|,交集&,差集-,對稱差分^
  5. 系統函數: add()/update()/remove()/discard()/pop()/clear()
  6. 案例1: 列表去重: l=[1,2,3,1,4,3,2,5,6,2];l=list(set(l)) (因爲集合無序,沒法保持原有順序)
  7. 案例2: 100w條數據,用列表和集合哪一個性能更好? - 集合性能要遠遠優於列表, 集合是基於哈希的, 不管有多少元素,查找元素永遠只須要一步操做, 而列表長度屢次就可能須要操做多少次(好比元素在列表最後一個位置)

6. 字典Dict

  1. 字典是由若干key-value對組成, 字典是無序的, 字典的key不能重複,並且必須是可哈希的,一般是字符串
  2. 字典操做
    • 賦值: d = {"a":1, "b":2}
    • 獲取: a = d['a']a = d.get("a") # d中不存在"a"元素時不會報錯
    • 增: d["c"] = 3; d.update({"d":5, "e": 6}
    • 刪: d.pop("d");d.clear() # 清空
    • 查: d.has_key("c")
    • 遍歷:
      • 遍歷key: for key in d:for key in d.keys():
      • 遍歷value: for value in d.values():
      • 遍歷key-value對: for item in d.items():
  3. 案例: 更新接口參數 api = {"url": "/api/user/login": data: {"username": "張三", "password": "123456"}},將username修改成"李四"
    api['data']['username'] = "李四"api['data'].update({"username": "李四"})redis

    哈希與可哈希元素算法

  4. 哈希是經過計算獲得元素的存儲地址(映射), 這就要求不一樣長度的元素都能計算出地址,相同元素每次計算出的地址都同樣, 不一樣元素計算的地址必須惟一, 基於哈希的查找永遠只須要一步操做, 計算一下獲得元素相應的地址, 不須要向序列那樣遍歷, 因此性能較好
  5. 可哈希元素: 爲了保證每次計算出的地址相同, 要求元素長度是固定的, 如數字/字符串/只包含數字,字符串的元組, 這些都是可哈希元素

6種類型簡單的特色總結

  1. 數字/字符串/元祖: 長度固定
  2. 序列(字符串/列表/元祖): 有序
  3. 集合/字典: 無序, 不重複/鍵值不重複

條件/循環

條件判斷

  1. 示例:
if x>0:
    print("正數")
elif x=0:
    print("0")
else: 
    print("負數")
  1. 三元表達式: max = a if a > b else b
  2. 案例: 判斷一個字符串是不ip地址

    ip_str = '192.168.100.3'
    ip_list = ip_str.split(".") # 將字符串按點分割成列表
    is_ip = True # 先假設ip合法
    if len(ip_list) != 4:
    is_ip= False
    else:
    for num in ip_list:
    if not isdigit(num) or not 0 <= int(num) <= 255:
    is_ip = False
    if is_ip:
    print("是ip")
    else:
    print("不是ip")

    使用map函數的實現方法(參考):

    def check_ipv4(str):
    ip = str.strip().split(".")
    return False if len(ip) != 4 or False in map(lambda x:True if x.isdigit() and 0<= int(x) <= 255 else False, ip) else True

    循環

  3. for in 循環
  4. while 循環

文件讀寫(文本文件)

html/xml/config/csv也能夠按文本文件處理

文件操做方法

  1. open(): 打開f =open("test.txt")f =open("test.txt","r", encoding="utf-8")with open("test.txt) as f: # 上下文模式,出結構體自動關閉文件
  2. read()/readline()/readlines(): 讀取全部內容/讀取一行/讀取全部行(返回列表) - 注意: 內容中包含\n換行符,能夠經過strip()去掉
  3. f.write()/f.save(): 寫文件/保存文件
  4. f.seek(): 移動文件指針,如f.seek(0), 移動到文件開頭
  5. f.close(): 關閉文件(打開文件要記得關閉)

文件打開模式

  1. r/w/a: 只讀/只寫/追加模式
  2. rb/wb/ab: 二進制只讀/只寫/追加模式(支持圖片等二進制文件)
  3. r+/rb+, w+/wb+, a+/ab+: 讀寫,區別在於, r+/w+會清空文件再寫內容, r+文件不存在會報錯, a+不清空原文件,進行追加, w+/a+文件不存在時會新建文件

    文件是可迭代的,能夠直接用 for line in f: 遍歷

函數/類

函數定義和調用

def add(x, y): # 定義函數
    return x+y

print(add(1,3)) # 調用函數

案例: 用戶註冊/登陸函數

users = {"張三": "123456"}

def reg(username, password):
    if users.get(username): # 若是用戶中存在username這個key
        print("用戶已存在")
    else:
        users[username] = password # 向users字典中添加元素
        print("添加成功")

def login(username, password)
    if not users.get(username):
        print("用戶不存在")
    elif users['username'] == password:
        print("登陸成功")
    else:
        print("密碼錯誤")

參數和返回值

  1. 函數沒有return默認返回None
  2. 參數支持各類對象,包含數字,支付串,列表,元組,也能夠是函數和類
  3. 參數默認值: 有默認值的參數必須放在最後面, 如```def add(x, y=1, z=2):
  4. 不定參數: *args和**kwargs, 如def func(*args, **kwargs):能夠接受任意長度和格式的參數
  5. 參數及返回值類型註釋(Python3)
def(x:int, y:int) -> int: # x,y爲int型,函數返回爲int型,只是註釋,參數格式非法不會報錯
    return x+y

函數做爲參數(如: 裝飾器)

def a():
    print("I'm a")
def deco(func):
    print("call from deco")
    func()

deco(a) # 輸出"call from deco"並調用a(),輸出"I'm a"

函數嵌套(支持閉包)

def a():
    a_var = 1
    def b:() # 嵌套函數
        a_var += 1

函數遞歸(本身調用本身,直到知足需求)

案例: 求N!

def factorial(n):
    return 1 if n == 0 or n == 1 else n * factorial(n-1)

模塊/包

模塊

  1. 一個py文件爲一個模塊
  2. 模塊導入
    • import os # 須要經過os調用相關方法, 如os.mkdir(),
    • form configparser import ConfigParser: 能夠直接使用CinfigParser()
    • 支持一次導入多個

  3. 一個文件夾爲一個包(Python3,文件夾中不須要創建__init__.py文件)

    經常使用系統模塊

  4. os: 與操做系統交互
    • os.name/os.sep/os.linesep: 系統名稱/系統路徑分隔符/系統換行符
    • os.makedir()/os.makedirs(): 創建目錄/創建多級目錄
    • os.getenv("PATH"): 獲取系統PATH環境變量的設置
    • os.curdir/os.prdir: 獲取當前路徑/上級路徑
    • os.walk(): 遍歷文件夾及子文件
    • os.path.basename()/os.path.abspath()/os.path.dirname(): 文件名/文件絕對路徑/文件上級文件夾名
    • os.path.join()/os.path.split(): 按當前系統分隔符(os.sep)組裝路徑/分割路徑
    • os.path.exists()/os.path.isfile()/os.path.isdir(): 判斷文件(文件夾)是否存在/是否文件/是否文件夾
    • 案例: 用例發現, 列出文件夾及子文件夾中全部test開頭的.py文件,並輸出文件路徑
    for root,dirs,files in os.walk("./case/"):
     for file in files:
         if file.startswith("test") and file.endswith(".py"):
             print(os.path.join(root, file)
  5. sys: 與Python系統交互
    • sys.path: 系統路徑(搜索路徑)
    • sys.platform: 系統平臺,能夠用來判斷是python2仍是3
    • sys.argv: py腳本接受的命令行參數
    • sys.stdin/sys.stdout/sys.stderr: 標準輸入/輸出/錯誤

常見算法

冒泡排序

def buddle_sort(under_sort_list):
    l = under_sort_list
    for j in range(len(l)):
        for i in range(len(l)-j-1):
           if l[i] > l[i+1]:
                l[i], l[i+1] = l[i+1], l[i]

快速排序

def quick_sort(l):
    if len(l) < 2:
        return l # 若是列表只有一個元素, 返回列表(用於結束迭代)
    else:
        pivot =  l[0] # 取列表第一個元素爲基準數
        low = [i for i in l[1:] if i < pivot] # 遍歷l, 將小於基準數pivot的全放入low這個列表
        high = [i for i in l[1:] if i >= pivot ]
        return quick_sort(low) + [pivot] + quick_sort(high) # 對左右兩部分分別進行迭代

二分查找

def bin_search(l, n): # l爲有序列表
    low, high = 0, len(l) - 1 # low,high分別初始化爲第一個/最後一個元素索引(最小/最大數索引)
    while low < high:
        mid = (high-low) // 2 # 地板除,保證索引爲整數
        if l[mid] == n:
            return mid
        elif l[mid] > n: # 中間數大於n則查找前半部分, 重置查找的最大數
            high = mid -1 
        else: # 查找後半部分, 重置查找的最小數
            low = mid + 1
    return None  # 循環結束沒有return mid 則說明沒找到

做業

  1. 查找文件中最長的行
  2. 統計文件中字符個數
  3. 判斷字符串"abacdbdde"中第一個不重複的字符
  4. 判斷字符串"{{({()[({})])}}"是否括號所有閉合

2、接口測試快速實踐

簡單接口搭建(表單/REST)

五步教會你寫接口

首先要安裝flask包: pip install flask

  1. 從flask中導入Flask類和request對象: from flask import Flask, request
  2. 從當前模塊實例化出一個Flask實例:app=Flask(__name__)
  3. 編寫一個函數來處理請求
    1. 從請求對象中獲取數據:a=request.values.get("a");b=request.values.get("b")
      • request.params: 字典格式,存儲請求中的url參數
      • request.form: 字典格式,存儲請求中的表單數據
      • request.values: 字典格式, 包含params和form中的值
      • request.json: 字典格式, 存儲json類型的請求數據, 若是請求類型非json, 值爲空
    2. 進行業務處理: sum = int(a) + int(b)
    3. 組裝並返回響應數據: return str(sum) # http通常使用字符串傳輸數據
  4. 爲接口指定接口地址和接受的方法:@app.route("/add/", methods=["GET"]) # 寫到函數上面(裝飾器)
  5. 運行接口:
    最後添加:
if __name__ == "__main__":
app.run()

保存爲add.py, 打開命令行,進入add.py所在目錄,運行python add.py

完整代碼

# 1. 導入包 
from flask import Flask, request
# 2. 實例化一個
app = Flask(__name__)
# 3. 編寫一個接口處理方法
@app.route("/add/", methods=["GET","POST"]) # 4. 掛載路由(指定接口的url路徑), 聲明接口接受的方法
def add():  
    # 3.1 從請求中獲取參數
    # request.values  {"a": "1", "b": "2"}
    a = request.values.get("a")
    b = request.values.get("b")
    # 3.2 業務操做
    sum = int(a) + int(b)
    # 3.3 組裝響應並返回
    return str(sum)

# 5. 運行接口
if __name__ == '__main__':
    app.run() # 默認5000端口,能夠指定端口app.run(port=50001)

REST類型接口實現

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/api/sub/", methods=["POST"])
def sub():
    if not request.json: # 若是請求數據類型非json
        return jsonify({"code": "100001", "msg": "請求類型錯誤", "data": None})

    if not "a" in request.json or not "b" in request.json: # 若是參數中沒有a或者沒有b
        return jsonify({"code": "100002", "msg": "參數缺失", "data": None})
    
    a = request.json.get("a")
    b = request.json.get("b")
    result = str(float(a) - float(b)) # 使用float支持浮點數相減
    return jsonify({"code": "100000", "msg": "成功", "data": result}) # 使用jsonify將字典數據轉換爲json類型的相應數據

if __name__ == '__main__':
    app.run()

使用Postman測試接口(Form/Json)

編寫接口文檔

接口測試基礎

接口測試概念

接口測試是測試系統組件間接口的一種測試。
接口測試主要用於檢測外部系統與系統之 間以及內部各個子系統之間的交互點。測試的重點是要檢查數據的交換,傳遞和控制管理過 程,以及系統間的相互邏輯依賴關係等。

接口測試目的

  • 核心:保證系統的穩定
  • 手段:持續集成
  • 目的:提升測試效率,提高用戶體驗,下降產品研發成本

接口測試通常流程

  • 列出需求
  • 安排資源,編寫接口用例 -> 用例評審
  • 編寫接口測試代碼 -> 代碼評審codeReview
  • 執行接口測試

接口測試關注點

  • 功能:功能實現,實現與設計一致, 接口經過性測試
  • 健壯性: 邊界值,容錯性
  • 性能: 併發及壓測
  • 穩定性: 長期運行的穩定性
  • 安全性: SQL注入, session依賴, 數字簽名, http接口的安全性

常見接口種類

  • Http/Https接口: 經過http/https協議傳送接口數據(一般按字符串/二進制傳輸), 如常見的網頁表單, https安全性更好
  • RESTful Api: REST表述性狀態傳遞. 一種設計風格,基於http/https協議, 把一切接口視爲資源, 接口要分版本,在統一的域名下管理, 不一樣的方法(get/post..)作不一樣的事,一般請求及響應使用json格式
  • Web Service: SOAP簡單面向對象協議, 基於http實現的一種RPC方案.接口返回一些對象,能夠直接經過操做對象,實現咱們須要的業務處理.使用xml格式傳輸數據
  • RPC接口: RPC爲遠程方法調用, 有不一樣的實現方案,基於TCP/Http協議的都有. RPC能夠想咱們本地導入和調用對象同樣使用. Dubbo接口也是一種RPC接口.

常見接口數據類型

  • 請求數據類型(Content-Type):
    • application/x-www-form-urlencoded: 常規只有文本的網頁表單
    • application/json: RESTful Api經常使用格式, 結構清晰, 含有多層嵌套
    • multipart/form-data: 既有文本,又有上傳文件或富文本框的混合數據表單
    • text/xml: xml格式, RPC接口經常使用格式
  • 響應數據類型
    • string/html: 返回字符串或網頁源碼
    • json: RESTful Api經常使用響應格式, 結構清晰
    • xml: RPC接口經常使用格式

常見接口安全驗證方式

  • Auth_1.0/Auth_2.0: 通用接口受權方式
  • Session依賴: 須要登陸以後才能進行接口操做
  • Token驗證: 先要使用本身的appid/appsecret經過獲取token接口驗證身份獲取一個token(令牌,有必定有效期), 而後帶着token訪問接口
  • 數字簽名: 將本來的參數按必定規則進行組合,配合時間戳或appsecret, 經過加密算法生成一個簽名sign, 攜帶簽名進行接口請求

常見接口請求方法

  • GET: 獲取資源
  • POST: 修改資源
  • PUT: 上傳資源
  • DELETE: 刪除資源
  • HEAD: 只請求頁面首部
  • PATCH: 補丁
  • OPTIONS: 運行客戶端查看服務器性能
    ......

常見狀態碼(RESTful規範)

  • 200系: 成功
    • 200 OK - [GET]:獲取資源成功
    • 201 CREATED - [POST/PUT/PATCH]:建立/修改爲功
    • 202 Accepted - [*]:任務接受
    • 204 NO CONTENT - [DELETE]:刪除成功
  • 300系: 重定向
    • 301 Moved Permanently: 永久重定向
    • 302 Found: 臨時重定向
  • 400: 資源錯誤
    • 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶請求錯誤
    • 401 Unauthorized - [*]:沒有權限(鑑權失敗, 接口層)
    • 403 Forbidden - [*] 資源禁止訪問(服務器層,沒有訪問權限)
    • 404 NOT FOUND - [*]:資源不存在
    • 405 Method Not Allowd: 訪問的方法不容許, 如用POST訪問只支持GET請求的接口
    • 406 Not Acceptable - [GET]:用戶請求的格式不可得(好比用戶請求JSON格式,可是隻有XML格式)
    • 410 Gone -[GET]:資源被永久刪除
    • 422 Unprocesable entity - [POST/PUT/PATCH] 當建立對象時,發生驗證錯誤
  • 500系: 服務器內部錯誤(接口崩潰或有Bug)
    • 500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤

接口業務類型

  • 返回數據型接口: 只從數據庫讀取數據
  • 業務操做型接口: 須要寫數據庫(接口測試須要要涉及參數化或環境清理)

快速上手接口測試

獲取接口文檔

  • Wiki
  • Word文檔
  • Postman導出
  • 抽象接口定義
  • 接口管理平臺

接口文檔分析

  • 功能分析: 是否能知足業務(是否缺乏某個前端須要的參數), 是否能知足全部業務場景(是否有漏開發接口, 好比只開發了單品接口,沒開發套餐接口)
  • 設計分析: 是否有不規範字段(如,nickname, passwd);不規範格式(如sex,用男,女而不是1,2);是否有易混淆字段(如amount和total);是否有單詞拼錯;是否有和數據庫字段對應但名稱不同的(易錯)
  • 接口分析: 協議類型(http要考慮安全);請求方法(是否規範);請求編碼格式(表單/Json/xml, 不少接口文檔不聲明,致使測試調試不通);接口受權方式;接口業務類型(關係到是否須要作參數化或環境清理); 返回值類型及結構(關係到怎麼斷言)
  • 接口依賴: 須要什麼環境準備和業務場景, 依賴那些接口, 有那些動態數據, 預備環境怎麼保障
  • 參數分析: 各個參數的參數類型,組成規則,是否容許不傳,是否能夠爲空, 是否容許多傳參
  • 業務分析: 如price字段必須和數據庫中的商品的price字段一致,才能校驗經過
  • 非功能性: 接口的技術實現方案是否合理, 可否知足高併發的性能要求, 邊界值/極限值的處理是否合適, 是否先後端都有數據格式校驗等(如精確度爲秒級的訂單號生成器,在高併發下會致使生成同一訂單號的問題)
  • 其餘: 如反爬,對headers的一些限制和校驗, ip等限制

編寫接口用例

Excel/TestLink/禪道

  • 單接口用例: 正常數據/邊界數據/異常數據(健壯性)/併發(一致性)/性能/安全性(抓包截取僞造/SQL注入/跨域請求)
  • 場景用例: 列出常見的用戶場景, 用接口進行覆蓋, 業務場景壓測(尋找某個環節的性能瓶頸)
TestCase Url Method DataType a b Excepted Actual Status
test_add_normal /api/add/ GET Url 3 5 8
test_add_zero /api/add/ POST FORM 0 0 0
test_add_negetive /api/add/ POST FORM -3 5 2
test_add_float /api/add/ POST FORM 3.2 5.2 8.4
test_add_null /api/add/ POST FORM 0

執行接口測試

  • Postman: 功能調試
  • Jmeter: 性能

接口自動化實踐

五步教會你寫接口自動化用例

須要安裝三方包:requests pytest pytest-htmlpip install requests pytest pytest-html

  1. 導入requests模塊
    import requests
  2. 組裝請求參數和數據
    url = 'http://127.0.0.1:5000/add/'
    params = {"a":3, "b":5} # get請求url參數, 字典格式
    data = {"a":3, "b":5} # post請求請求數據, 字典格式
  3. 發送請求獲得response對象
    resp = requests.get(url=url, params=params)
    resp = requests.post(url=url,data=data)
  4. 解析response對象
    • resp.text # 獲取響應文本
  5. 斷言結果
    assert resp.text == '8'
    完整代碼:
# 1. 導入包
import requests

base_url = "http://127.0.0.1:5005"

# 2. 組裝請求
def test_add_normal():
    # url  字符串格式
    url = base_url + "/add/"
    
    # data {} 字典格式
    data = {"a": "1", "b": "2"}
    # 3. 發送請求,獲取響應對象
    response = requests.post(url=url, data=data)
# 4. 解析響應
# 5. 斷言結果
    assert response.text == '3'

REST類接口自動測試方法

請求格式爲json

三處不一樣:

  1. 必須經過headers指定內容類型爲application/json: ```headers={"Content-Type":"application/json"}
  2. 請求數據要轉化爲字符串: data=json.dumps(data) (使用json.dumps須要import json)
  3. json格式的響應數據,在接口調試經過和穩定的狀況下可使用response.json()解析爲字典格式,進行斷言

完整代碼:

# 1. 導入包
import requests
import json

base_url = "http://127.0.0.1:5005"

def test_sub_normal():
    url = base_url + "/api/sub/"
    headers = {"Content-Type": "application/json"} # 1. 必須經過headers指定請求內容類型爲json
    data = {"a": "4", "b": "2"}
    data = json.dumps(data) # 2. 序列化成字符串
    response = requests.post(url=url, headers=headers, data=data)
    # 3. 響應解析 # 響應格式爲: {"code":"100000", "msg": "成功", "data": "2"}
    resp_code = response.json().get("code") 
    resp_msg = response.json().get("msg")
    resp_data = response.json().get("data")
    # 斷言
    assert response.status_code == 200
    assert resp_code == "100000"
    assert resp_msg == "成功"
    assert resp_data == "2"

補充1: 感覺Python黑科技之exec()動態生成用例:

數據文件: test_add_data.xls

TestCase Url Method DataType a b Excepted Actual Status
test_add_normal /api/add/ GET Url 3 5 8
test_add_zero /api/add/ POST FORM 0 0 0
test_add_negetive /api/add/ POST FORM -3 5 2
test_add_float /api/add/ POST FORM 3.2 5.2 8.4
test_add_null /api/add/ POST FORM 0
import requests
import xlrd

base_url = 'http://127.0.0.1:5005'

# 1.打開excel
wb = xlrd.open_workbook("test_add_data.xls")
# 2. 獲取sheet
sh = wb.sheet_by_index(0)  # wb.sheet_by_name("Sheet1")
# 行數 sh.nrows 列數 sh.ncols
# 獲取單元格數據
# print(sh.cell(1,0).value)
# print(sh.nrows)
tpl = '''def {case_name}():
    url = base_url + '/add/'
    data = {{"a": "{a}", "b":"{b}"}}
    response = requests.get(url=url, data=data)
    assert response.text == '{expected}'
'''

for row in range(1, sh.nrows):
    case_name = sh.cell(row,0).value
    a = sh.cell(row, 4).value
    b = sh.cell(row, 5).value
    expected = sh.cell(row, 6).value
    case = tpl.format(case_name=case_name, a=a, b=b, expected=expected)
    exec(case)

動態生成的用例支持pytest發現和執行

自動化接口用例運行

  1. 將自動化測試用例保存爲test*.py,一個文件裏能夠寫多個用例, 如上面兩個接口保存爲test_user.py
  2. 在自動化用例腳本所在目錄打開命令行, 運行pytest(運行全部test開頭的.py用例)或pytest test_user.py(只運行該腳本中用例)
  3. pytest一些參數
    • -q: 安靜模式(不顯示環境信息) pytest -q test_user.py
    • --html=report.html:執行完生成report.html報告(文件名能夠本身指定)pytest test_user.py --html=test_user_report.html
    • --resultlog=test.log: 執行完成生成執行結果log文件pytest test_user.py --resultlog=run.log

requests庫詳解

請求方法

  • requests.get()
  • requests.post()
  • requests.delete()
  • .....
  • requests.session() # 用來保持session會話,如登陸狀態

    請求參數

  • url: 接口地址, str url="http://127.0.0.1:5000/add/"
  • headers: 請求頭, dict headers={"Content-Type": "application/json"}
  • params: url參數, dict params={"a":"1":"b":"2"}
  • data: 請求數據, dict data={"a":"1":"b":"2"}
  • files: 文件句柄, dict files={"file": open("1.jpg")}
  • timeout: 超時時間,單位s, str, 超過期間會報超時錯誤```requests.get(url=url,params=params,timeout=10)

    響應解析

  • resp: 響應對象
  • resp.status_code: 響應狀態碼
  • resp.text # 響應文本
  • resp.json() # 響應轉化爲json對象(字典)-慎用:若是接口出錯或返回格式不是json格式,使用這個方法會報錯
  • resp.content # 響應內容, 二進制類型

3、接口安全驗證,參數化及斷言

各類類型接口的測試

GET請求接口

requests.get(url=url, params=params)

表單類型

requests.post(url=url, data=data)

REST類型

requests.post(url=url, headers={"Content-Type": "application/json"}, data=json.dumps(data)

上傳文件

requests.post(url=url, files={"file": open("1.jpg", "rb")})

Session依賴

session=requests.session(); session.post(...); session.post()

接口依賴

  1. 接口依賴之動態中間值
resp=requests.get(...);token=resp.split("=")[1];resp2=requests.post(....token...)

驗籤接口

import hashlib

def md5(str):
    m = hashlib.md5()
    m.update(str.encode('utf8'))
    return m.hexdigest()  #返回摘要,做爲十六進制數據字符串值

def makeSign(params):
    if not isinstance(params, dict):
        print("參數格式不正確,必須爲字典格式")
        return None
    if 'sign' in params:
        params.pop('sign')
    sign = ''
    for key in sorted(params.keys()):
        sign = sign + key + '=' + str(params[key]) + '&'
    sign = md5(sign + 'appsecret=' + appsecret)
    params['sign'] = sign
    return params
data = makeSign(data);resp = requests.post(url=url, headers=headers, data=json.dumps(data))
  1. 接口依賴之Mock Server

Mock和單元測試的樁(Stub)相似, 是經過創建一個模擬對象來解決依賴問題的一種方法.

應用場景:
1. 依賴第三方系統接口, 沒法調試
2. 所依賴接口還沒有具有(先後端分離項目, 前端開發時,後端接口還沒有開發完畢)
3. 所依賴接口須要修改或不穩定
4. 依賴接口較多或場景複雜, 所依賴接口不是主要驗證目標的

解決方法:
1. 經過Mock.js/RAP/RAP2來動態生成, 模擬接口返回數據
2. 本身使用Flask你們簡單的Mock接口
3. 使用Python自帶的mock庫

...

SOAP接口

pip install suds

from suds.client import Client

ip = '127.0.0.1'
port = '5001'

client = Client("http://%s:%s/?wsdl" % (ip, port))
result = client.service.addUser("張790", "123456")
print(result)

XML-RPC接口

import xmlrpc.client

user = xmlrpc.client.ServerProxy('http://127.0.0.1:5002')
print(user.getAll())

參數化

參數化是用來解決動態參數問題的

數據文件參數化

  • csv數據文件
    • 優勢:以逗號分隔,輕量級
    • 缺點:參數過多不便於區分
import csv

csv_data = csv.reader(open('data/reg.csv'))
  • config數據文件
    • 優勢:方便支持多組數據,支持備註
    import configparser
      cf=configparser.ConfigParser()
      cf.read('data/reg.config', encoding='utf-8')
      cf.sections()
      cf.options(section)
      cf.get(section,option)
  • json數據文件
    • 優勢:REST接口經常使用數據格式,格式清楚,適用於多參數及含有嵌套參數
    • 缺點:不支持備註,多組數據不清晰
    import json
    with open('data/reg.json', encoding='utf-8') as f:
      json_data = json.loads(f)  #json_data爲列表或字典
json的序列化和反序列化
需求:python的字典/列表等數據格式爲內存對象,須要作存儲(持久化)和進行數據交換

序列化: 從內存對象到可存儲數據, 方便存儲和交換數據
    json.dumps: 列表/字典/json->字符串 ```str_data = json.dumps(dict_data)```
    json.dump: 列表/字典/json->數據文件 ```json.dump(dict_data, open(data_file, "w"))```
反序列化: 從存儲數據到內存對象
    json.loads: 字符串->字典/列表```json.loads('{"a":1,"b":2}') #獲得字典{"a":1,"b":2}```
    json.load: json數據文檔->列表/字典```dict_data = json.load(open('data.json'))```
  • excel數據文件
    • 優勢:直觀,構造數據方便
    • 缺點:嵌套數據不方便格式化

pip install xlrd

import xlrd

wb=xlrd.open_workbook("data/reg.xlsx")
sh=wb.sheet_by_index(0)
sh=wb.sheet_by_name('Sheet1")
sh.nrows
sh.ncols
sh.cell(x,y).value
  • xml數據文件
    • 優勢:方便自定義多層結構,SOAP,RPC通用格式
    from xml.dom.minidom import parse
    dom=parse('data/reg.xml')
    root=dom.documentElement
    user_nodes=root.getElementsByTagName("user")
    user_node.getAttribute('title')
    user_node.hasAttribute('title')
    name_node=user_node.getElementsByTagName('name')[0]
    name=name_node.childNodes[0].data

    案例1: 自動執行excel用例並將結果回寫excel

數據文件: test_user.xlsx

TestCase Url Method DataType Data Excepted Resp.text Status
test_user_reg_normal /api/user/reg/ POST JSON {"name":"九小1","passwd": "123456"} resp.json()["code"]=="100000"
test_user_login_normal /api/user/login/ POST FORM {"name":"九小1","passwd": "123456"} "成功" in resp.text
import xlrd
from xlutils.copy import copy
import json
import requests
import sys

base_url = "http://127.0.0.1:5000"

def run_excel(file_name, save_file="result.xls"):
    wb=xlrd.open_workbook(file_name)
    sh=wb.sheet_by_index(0)

    wb2 = copy(wb)
    sh2 = wb2.get_sheet(0)


    for i in range(1,sh.nrows):
        url = base_url + sh.cell(i,1).value
        data = json.loads(sh.cell(i,4).value)
        headers = {}
        method = sh.cell(i,2).value
        data_type = sh.cell(i,3).value
        excepted = sh.cell(i,5).value
        if data_type.lower() == 'json':
            data = json.dumps(data)
            headers = {"Content-Type":"application/json"}

        if method.lower() == "get":
            resp = requests.get(url=url,headers=headers)
        else:
            resp = requests.post(url=url,headers=headers,data=data)
        if eval(excepted):
            status = "PASS"
        else:
            status = "FAIL"
        sh2.write(i,6, resp.text)
        sh2.write(i,7, status)

    wb2.save(save_file)
    print("保存成功")
        
        
if __name__ == "__main__":
    if len(sys.argv)==2:
        run_excel(sys.argv[1])
    elif len(sys.argv)>2:
        run_excel(sys.argv[1],sys.argv[2])
    else:
        print("調用格式: python run_excel.py 用例文件 輸出文件")

保存腳本爲run_excel.py, (最好和數據文件test_user.xlsx放在同一目錄下), 在腳本所在目錄打開命令行,運行

python run_excel.py test_user.xlsx

生成的result.xls預覽

TestCase Url Method DataType Data Excepted Resp.text Status
test_user_reg_normal /api/user/reg/ POST JSON {"name":"九小1","passwd": "123456"} resp.json()["code"]=="100000" {"code":"100001","data":{"name":"\u4e5d\u5c0f1","passwod":"e10adc3949ba59abbe56e057f20f883e"},"msg":"\u5931\u8d25\uff0c\u7528\u6237\u5df2\u5b58\u5728"} FAIL
test_user_login_normal /api/user/login/ POST FORM {"name":"九小1","passwd": "123456"} "成功" in resp.text <h1>登陸成功</h1> PASS

隨機數據參數化

import random

  • 隨機數
    • random.random(): 隨機0,1
    • random.randint(0,100): 隨機整數
    • random.randrange(0,100,2): 隨機偶數
    • random.uniform(1,100): 隨機浮點數
  • 隨機選擇
    • random.choice('abcdefg')
    • random.choice(['趙','錢','孫','李','周'])
    隨機姓名的實現:
    #隨機漢字: chr(random.randint(0x4e00, 0x9fbf))
    name=random.choice(['趙','錢','孫','李','周'])+chr(random.randint(0x4e00, 0x9fbf)
  • 隨機樣本
    • random.sample('abcdefg', 2)

隨機2個字符拼接: ''.join(random.sample('abcdefg',2)

  • 洗牌
    • random.shuffle([1, 2, 3, 4, 5, 6]): 隨機改版列表數據

斷言/檢查點

斷言/檢查點是用來自動驗證接口返回數據/業務操做的結果是否知足預期

響應斷言

正則表達式

  • 元字符
    • . : 任意字符
    • \d: 任意數字 - \D: 任意非數字
    • \w: 任意字符或數字 - \W: 任意非字符及數字
    • \s: 任意空白字符(包含空格或\n等) - \S: 任意非空白字符
    • ^: 匹配開頭
    • $: 匹配結尾
    • {n,m}: 匹配n-m個重複
      • : 匹配重複任意次(包含0次)
      • : 匹配重複至少一次
    • ? : 匹配0或1次
    • (): 分組,獲取須要的部分數據
    • | : 或, 匹配多個pattern
    • \元字符: 取消元字符轉義
  • 貪婪匹配及非貪婪匹配
  • 系統函數
    • re.findall(): re.S,支持跨行
    • re.match()
    • re.search()
    • re.sub()/re.subn()
    • re.complie()

      數據庫斷言

      從數據庫讀取數據,驗證數據庫數據是否符合預期

  • MySQL斷言

    pip install pymysql

  1. 導入pymysql: import pymysql
  2. 創建數據庫鏈接:
conn = pymysql.connect(host='',port=3306,db='',user='',passwd='',charset='utf8')
  1. 從鏈接創建操做遊標: cur=conn.cursor()
  2. 使用遊標執行sql命令: cur.execute("select * from user")
  3. 獲取執行結果:
    1. cur.fetchall(): 獲取全部結果
    2. cur.fetchmany(3): 獲取多條結果
    3. cur.fetchone(): 獲取一條結果
  4. 關閉遊標及鏈接(先關遊標再關鏈接):cur.close();conn.close()
  • PostgreSQL

    pip install pyscopg2

import pyscopg2
conn=pyscopg2.connect(host='',port='',dbname='',user='',password='') # 注意是dbname, password
cur=conn.curser()
cur.execute("...")
cur.fetchall()
  • Oracle

    pip install cx_Oracle

...
  • Mongodb

    pip install pymongo

from pymongo import MongoClient

conn = MongoClient('', 27017)
db = conn.mydb
my_set = db.test_set

for i in my_set.find({"name": "張三"}):
    print(i)

print(my_set.findone({"name": "張三"}))
  • Redis斷言

    pip install redis

import redis

r = redis.Redis(host='192.168.100.198', port=6379, password='!QE%^E2sdf23RGF@ml239', db=0)
print(r.dbsize())
print(r.get('package'))
print(r.hget('event_order_advance_008aea6a62115ec4923829ee09f76a9c18243f5d', 'user'))

服務器斷言

pip install paramiko

import paramiko

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())  
client.connect('192.168.100.241', 22, username='root', password='1234567', timeout=4)
stdin, stdout, stderr = client.exec_command('cat /proc/meminfo')
print(stdout.read())
client.close()

完整用例:

import requests
import pytest
import json
import hashlib
import re


def md5(string):
    m = hashlib.md5()
    m.update(string.encode('utf8'))
    return m.hexdigest()

def makeSign(data):
    sign=''
    for key in sorted(data.keys()):
        sign += key + '=' + str(data[key]) + '&'
    sign += 'appsecret=NTA3ZTU2ZWM5ZmVkYTVmMDBkMDM3YTBi'
    data['sign'] = md5(sign)
    return data

class DB():
    def __init__(self):
        # 創建鏈接
        self.conn = pymysql.connect(host='localhost',port=3307,user='root',passwd='',db='api',charset='utf8')

        # 創建一個遊標
        self.cur = self.conn.cursor()
    
    def __del__(self):
        self.cur.close()
        self.conn.close()

    def getUserByName(self, name):
        self.cur.execute("select * from user where name='%s'" % name)
        return self.cur.fetchone()

    def checkUser(self, name, passwd):
        user = self.getUserByName(name)
        if user:
            if user[2] == md5(passwd):
                return True
            else:
                return False
        else:
            return None
            
class TestUser(): # pytest識別不能用__init__方法
    base_url = 'http://127.0.0.1:5000'
    db = DB()

    def test_login(self):
        url = self.base_url + '/api/user/login/'
        data = {"name": "張三", "passwd": "123456"}
        resp = requests.post(url=url, data=data)

        #斷言
        assert resp.status_code == 200
        assert '登陸成功' in resp.text

    def test_reg(self):
        url = self.base_url + '/api/user/reg/'
        headers = {"Content-Type": "application/json"}
        data = {'name': '張10', 'passwd': '123456'}
        resp = requests.post(url=url, headers=headers, data=json.dumps(data))

        #斷言
        assert resp.json()['code'] == '100000'
        assert resp.json()['msg'] == '成功'
        assert self.db.getUserByName('張10')


    def test_uploadImage(self):
        url = self.base_url + '/api/user/uploadImage/'
        files = {'file': open("複習.txt")}
        resp = requests.post(url=url, files=files)

        #斷言
        assert resp.status_code == 200
        assert '成功' in resp.text
        # todo 服務器斷言

    def test_getUserList(self):
        session = requests.session()
        login_url = self.base_url + '/api/user/login/'
        login_data = {"name": "張三", "passwd": "123456"}
        session.post(url=login_url, data=login_data)

        url = self.base_url + '/api/user/getUserList/'
        resp = session.get(url=url)

        #斷言
        assert resp.status_code == 200
        assert '用戶列表' in resp.text
        assert re.findall('\w{32}',t2) != []

    def test_updateUser(self):
        session = requests.session()  # 接口依賴的接口須要用session
        get_token_url = self.base_url + '/api/user/getToken/'
        params = {"appid": '136425'}
        token_resp = session.get(url=get_token_url, params=params)
        assert re.findall('token=\w{32}$')
        token = token_resp.text.split('=')[1]

        url = self.base_url + '/api/user/updateUser/?token=' + token
        data = {'name': '張三', 'passwd': '234567'}
        headers = {"Content-Type": "application/json"}
        resp = session.post(url=url, headers=headers, data=json.dumps(data))

        #斷言
        assert resp.status_code == 200
        assert resp.json()['code'] == '100000'
        assert resp.json()['msg'] == '成功'
        assert self.db.checkUser('張三', '234567')

    def test_delUser(self):
        url = self.base_url + '/api/user/delUser/'
        headers = {"Content-Type": "application/json"}
        data = {'name': '張10', 'passwd': '123456'}
        data = makeSign(data)
        resp = requests.post(url=url, headers=headers, data=json.dumps(data))

        #斷言
        assert resp.status_code == 200
        assert resp.json()['code'] == '100000'
        assert resp.json()['msg'] == '成功' 
        assert not self.db.getUserByName('張10')


if __name__ == '__main__':
    t = TestUser()
    # t.test_updateUser()
    # t.test_updateUser()
    t.test_delUser()
    # pytest.main("-q test_user2.py")

4、接口測試框架實現

什麼是框架

目前主流接口測試方案

  • 工具派
  • Java派
  • Python派
  • 接口平臺

框架類型

  • 錄製回放
  • 數據驅動
  • 行爲驅動

框架的分層與規劃

框架分層

  • 表示層: (用戶界面)
  • 業務邏輯層: (讀取數據,配置並組裝發送請求)+執行控制層(pytest)
  • 數據層: (配置讀取/數據讀取/數據庫鏈接/其餘(log/email)

框架規劃

- case: 測試用例目錄
    - user: (用戶模塊)
        - test_user.py: 測試用例
    - case.py: 用例公共方法 
- data: 數據文件目錄
    - test_user_data.xlsx: 測試用例數據文件
- conf: 配置文件目錄
    - default.conf: 默認配置文件
- report: pytest生成的報告保存路徑
- log: log保存路徑,按天生成log
- common: 公共方法目錄
    - config.py: 配置文件讀取
    - data.py: 數據文件讀取
    - db.py: 數據庫鏈接
    - log.py: 日誌配置
    - send_email.py: 發送郵件配置

框架實現

conf/default.conf: 表示層-項目配置文件

[runtime]
log_level=debug
report_dir=report
log_dir=log
timeout=10

[server]
test = http://127.0.0.1:5000
stage = http://127.0.0.1:6000
prod = http://127.0.0.1:7000

[db_test]
host = localhost
port = 3307
db = api
user = root
passwd = 

[db_stage]

[db_prod]

[email]
server = smtp.sina.com
user = test_results@sina.com
pwd =  ******
subject = Api Test Ressult
receiver = superhin@126.com

data/test_user_data.xlsx: 表示層-用例數據文件

  • reg表(sheet名爲reg)
TestCase Url Method DataType Data Code Msg
test_reg_normal /api/user/reg/ POST JSON {"name": "{NAME}", "passwd": "123456"} 100000 成功
  • login表(sheet名爲login)
TestCase Url Method DataType Data ResponseText
test_login_normal /api/user/login/ POST FORM {"name": "張三", "passwd": "123456"} 登陸成功
  • SQL表(sheet名爲SQL)
checkUser select * from user where name={NAME}
checkUserPasswd select * from user where name={NAME} and passwd={PASSWD}

common/config.py:數據層-config文件讀取

"""
1. 從配置文件中獲取各個段信息
2. 返回一個項目的絕對路徑
"""
import os
import configparser

# 相對導入包的問題

pro_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


class Config(object):
    def __init__(self, filename="default.conf"):
        self.cf = configparser.ConfigParser()
        self.cf.read(os.path.join(pro_path,"conf",filename))
        
    def get_runtime(self, option):
        return self.cf.get("runtime", option)

    def get_server(self, option):
        return self.cf.get("server", option)

    def get_db_test(self, option):
        return self.cf.get("db_test", option)

    def get_email(self, option):
        return self.cf.get("email",option)

if __name__ == "__main__":
    c = Config()
    print(c.get_runtime("log_level"))
    print(c.get_server("test"))

data.py: 數據層-數據文件讀取

"""
1. 從Excel中讀取接口的數據
2. 讀取Sql命令
"""
import xlrd
import sys
import os
sys.path.append("..")
from common.config import pro_path

class Data(object):
    def __init__(self, filename):
        data_file_path = os.path.join(pro_path,"data",filename)   
        self.wb = xlrd.open_workbook("../data/test_user_data.xlsx")

    def get_case(self,sheet_name, case_name):
        sh = self.wb.sheet_by_name(sheet_name)
        for i in range(1, sh.nrows):
            if sh.cell(i,0).value == case_name:
                return sh.row_values(i)

        print("用例名未找到")
        return None

    def get_sql(self, sql_name):
        sh = self.wb.sheet_by_name("SQL")
        for i in range(sh.nrows):
            if sh.cell(i,0).value == sql_name:
                return sh.cell(i,1).value
        print("sql未找到")
        return None

if __name__ == "__main__":
    d = Data("test_user_data.xlsx")
    print(d.get_case("reg","test_reg_normal"))
    print(d.get_sql("checkUser"))

db.py: 數據層-數據庫鏈接

"""
1. 從配置文件中讀取數據庫配置
2. 鏈接數據庫
3. 執行sql並返回全部結果
"""
import sys
import pymysql
sys.path.append("..")
from common.config import Config

class DB(object):
    def __init__(self):
        c = Config()
        self.conn = pymysql.connect(host=c.get_db_test("host"),
                                    port=int(c.get_db_test("port")),
                                    db=c.get_db_test("db"),
                                    user=c.get_db_test("user"),
                                    passwd=c.get_db_test("passwd"),
                                    charset="utf8")

        self.cur = self.conn.cursor()

    def do_sql(self, sql):
        self.cur.execute(sql)
        return self.cur.fetchall()

    def __del__(self):
        self.cur.close()
        self.conn.close()

if __name__ == "__main__":
    db = DB()
    print(db.do_sql("select * from user"))

log.py: 數據層-log配置

"""
1. 配置log輸出格式 time - loglevel - file - func - line - msg
2. 支持輸出到log文件及屏幕
3. 支持返回一個logger,讓其餘模塊調用
"""
import sys
sys.path.append("..")

from common.config import Config, pro_path
import time
import logging
import os

class Log():
    @classmethod
    def config_log(cls):
        cf = Config()
        log_dir = os.path.join(pro_path, cf.get_runtime("log_dir"))
        today = time.strftime("%Y%m%d", time.localtime(time.time()))
        log_file = os.path.join(log_dir, today+".log")

        # 獲取一個標準的logger, 配置loglevel
        cls.logger = logging.getLogger()
        cls.logger.setLevel(eval("logging." + cf.get_runtime("log_level").upper()))

        # 創建不一樣handler
        fh = logging.FileHandler(log_file, mode="a",encoding=‘utf-8’)
        ch = logging.StreamHandler()

        # 定義輸出格式
        ft = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
        fh.setFormatter(ft)
        ch.setFormatter(ft)

        # 把定製handler 添加到咱們logger
        cls.logger.addHandler(fh)
        cls.logger.addHandler(ch)

    @classmethod
    def get_logger(cls):
        cls.config_log()
        return cls.logger

if __name__ == "__main__":
    l= Log.get_logger()
    l.info("abc")
    l.debug("hello, debug")

send_email.py: 數據層-郵件服務器鏈接

"""
1. 從配置文件中讀取stmp配置
2. 從report文件夾下打開report.html,發送郵件
"""
import smtplib
from email.mime.text import MIMEText
import os
import sys
sys.path.append("..")
from common.config import Config, pro_path
from common.log import Log


def send_email(report_name):
    cf = Config()
    logger = Log.get_logger()
    report_file = os.path.join(pro_path, cf.get_runtime("report_dir"),report_name)

    with open(report_file, "rb") as f:
        body = f.read()

    # 格式化email正文
    msg = MIMEText(body, "html", "utf-8")

    # 配置email頭
    msg["Subject"] = cf.get_email("subject")
    msg["From"] = cf.get_email("user")
    msg["To"] = cf.get_email("receiver")

    
    # 鏈接smtp服務器,發送郵件
    smtp = smtplib.SMTP()
    smtp.connect(cf.get_email("server"))
    smtp.login(cf.get_email("user"),cf.get_email("pwd"))
    smtp.sendmail(cf.get_email("user"), cf.get_email("receiver"), msg.as_string())
    print("郵件發送成功")

if __name__ == "__main__":
    send_email("report.html")

case/case.py: 業務邏輯層, 爲用例執行封裝方法

"""
1. 加載數據
2. 發送接口
3. 爲用例封裝一些方法

"""
import sys
sys.path.append("..")
from common.log import Log
from common.config import Config
from common.db import DB
from common.data import Data
import json
import requests


class Case(object):
    def __init__(self):
        self.logger = Log.get_logger()
        self.cf = Config()

    def load_data(self, data_file):
        self.data = Data(data_file)

    def set_env(self, env):
        self.env = env

    def run_case(self, sheet_name, case_name, var={}):
        case_data = self.data.get_case(sheet_name, case_name)

        url = self.cf.get_server(self.env) + case_data[1]
        data = case_data[4].format(**var)
        
        if case_data[3].lower() == "form":
            data = json.loads(data)
            headers = {}
        else:
            headers = {"content-type": "application/json"}

        if case_data[2].lower() == "get":
            resp = requests.get(url=url)
        else:
            resp = requests.post(url=url, headers=headers, data=data)
        return resp.text
        
    def check_response(self):
        pass
    
    def check_db(self, sql_name, vars={}):
        sql = self.data.get_sql(sql_name).format(**vars)
        return self.db.exec_sql(sql)
        

if __name__ == "__main__":
    c = Case()
    c.set_env("test")
    c.load_data("test_user_data.xlsx")
    r = c.run_case("login", "test_login_normal")
    print(r)

case/user/test_user.py: 表示層: 測試用例腳本

import sys
import random
import pytest
sys.path.append("../..")
from case.case import Case

case = Case()

def setup_module(module):
    case.set_env('dev')
    case.load_data('test_user_data.xlsx')

def test_login_normal():
    result case.run("login", "test_login_normal")

if __name__ == '__main__':
    pytest.main(["-q", "test_user.py"])

run_all.py: 表示層: 執行全部用例入口

import os
import time
from util.config import Config
from util.e_mail import send_email
import pytest

def main():
    cf = Config()
    report_dir = cf.get_report_dir()
    now = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
    report_name = os.path.join(report_dir, 'report_' + now + '.html')
    pytest.main(["-q", "case", "--html=" + report_name])
    send_email(report_name)

if __name__ == '__main__':
    main()
相關文章
相關標籤/搜索