excel+requests管理測試用例接口自動化框架

 

背景:數據庫

某項目有多個接口,以前使用的unittest框架來管理測試用例,將每一個接口的用例封裝成一個py文件,接口有數據或者字段變更後,須要去每一個py文件中找出變更的接口測試用例,維護起來不方便,爲了便於接口變更後維護,使用excel來管理測試用例,接口有變更不須要修改代碼,只須要維護excel便可。json

思路:api

爲了方便維護測試用例,一個接口的測試用例使用一個excel文件來管理,每一個excel文件中有兩個sheet頁,第一個sheet頁是接口的基本信息,包括接口名稱,地址和請求方式,第二個sheet頁爲接口的測試用例,以下圖所示cookie

第一個sheet頁session

第二個sheet頁app

 接口請求的數據類型爲X-WWW-FORM-URLENCODED,在測試用例中每一個字段爲一列,每條用例爲一行,倒數第二列爲預期結果,倒數第三列爲該條用例的描述。框架

接口自動化框架結構:post

common目錄存放公共的方法,例如寫日誌,連數據庫測試

config目錄存放配置文件和讀取配置文件內容的方法,cfg.ini包括髮送郵件的配置信息和接口的ip和端口字體

data目錄存放接口的測試用例

logs目錄存放用例執行的日誌

report目錄存放測試報告

run_main.py爲用例執行的入口

 

源碼:

api_test.py封裝讀取測試用例的數據,執行測試用例和校驗測試結果

#coding:utf-8

import xlrd,os
import requests
from datetime import datetime
from xlrd import xldate_as_tuple
from config import readConfig
from common.logger import Log

'''
獲取測試用例data所在的目錄
'''
d = os.path.dirname(__file__) #返回當前文件所在目錄(common文件夾路徑)
parent_path = os.path.dirname(d) #返回common的父級目錄
data_path = os.path.join(parent_path,'data') #返回data所在目錄
data_path1 = os.listdir(data_path) #返回data目錄下全部的文件

log = Log()

def api_data():
    for filename in data_path1:
        book = xlrd.open_workbook(os.path.join(data_path,filename))

        '''
        獲取excel文件中接口信息
        '''
        table = book.sheet_by_index(0) #經過索引,獲取相應的列表,這裏表示獲取excel的第一個列表
        inf_name = table.row_values(1)[0] #返回接口名稱
        inf_address = table.row_values(1)[1] #返回接口地址
        inf_mode = table.row_values(1)[2] #返回請求方式

        '''
        獲取excel文件中測試用例信息
        '''
        sheet = book.sheet_by_index(1) #經過索引,獲取相應的列表,這裏表示獲取excel的第二個列表
        nrows = sheet.nrows #獲取全部行數
        filed = sheet.row_values(0)
        # print(filed)

        for i in range(1,nrows):
            d1 = {}
            for j in range(0,len(filed)-2):
                ctype = sheet.cell(i, j).ctype  # 表格的數據類型
                cell = sheet.cell_value(i, j)
                d = {}
                if ctype == 2 and cell % 1 == 0:  # 若是是整形
                    cell = int(cell)
                elif ctype == 3:
                    # 轉成datetime對象
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime('%Y/%m/%d')
                elif ctype == 4:
                    cell = True if cell == 1 else False
                # print(cell)
                d.update({filed[j]:cell})
                # print(d)
                d1.update(d)
            # print(d1)

            '''
            獲取excel文件中測試用例預期結果和描述
            '''
            a = []
            for k in range(len(filed)-2,len(filed)):
                ctype = sheet.cell(i, k).ctype  # 表格的數據類型
                cell = sheet.cell_value(i, k)
                if ctype == 2 and cell % 1 == 0:  # 若是是整形
                    cell = int(cell)
                elif ctype == 3:
                    # 轉成datetime對象
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime('%Y/%m/%d')
                elif ctype == 4:
                    cell = True if cell == 1 else False
                a.append(cell)
            # print(a[0])
            # print(type(a[0]))

            '''
            獲取cfg.ini配置文件中接口公共信息(ip和port)
            '''
            ip = readConfig.ip  # 獲取配置文件中接口ip
            i_port = readConfig.i_port  # 獲取配置文件中接口port
            url = "http://" + ip + ":" + i_port + inf_address
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
                "X-Requested-With": "XMLHttpRequest",
                "Connection": "keep-alive"
            }
            par = d1

            '''
            判斷請求方式是GET仍是POST,而且判斷測試用例預期結果與實際響應一致
            '''
            if inf_mode == 'GET':
                r = requests.get(url, params=par)
                result = r.json()
                # log.info("---編號%s,接口名稱%s---")%(i,inf_name)
                print(inf_name, str(result).replace('None','null'), a[1])
                if str(result).replace('None','null') == a[0]:
                    log.info("pass")
                    log.info("--------")
                else:
                    log.info("false")
                    log.info("--------")
            elif inf_mode == 'POST':
                r = requests.post(url, data=par, headers=headers)
                result = r.json()
                print(inf_name, str(result).replace('None', 'null'), a[1])
                if str(result).replace('None', 'null') == a[0]:
                    print('pass')
                    print('--------')
                else:
                    print('false')
                    print('--------')


api_data()

配置文件cfg.ini(主要配置郵箱和接口ip+port等經常使用數據信息)

[email]

smtp_server = smtp.163.com
port = 465
sender = xxx;psw是QQ郵箱的受權碼
psw = xxx

;收件人多個時,中間用逗號隔開,如'a@xx.com,b@xx.com'
receiver = xxx[interface]

ip = xxx
;接口ip
port = xxx
;接口端口

readConfig.py讀取配置文件中數據

# coding:utf-8
import os
import configparser

cur_path = os.path.dirname(os.path.realpath(__file__))
configPath = os.path.join(cur_path, "cfg.ini")
conf = configparser.ConfigParser()
conf.read(configPath,encoding='utf-8')


smtp_server = conf.get("email", "smtp_server")

sender = conf.get("email", "sender")

psw = conf.get("email", "psw")

receiver = conf.get("email", "receiver")

port = conf.get("email", "port")

ip = conf.get("interface","ip")

i_port = conf.get("interface","port")

 

優化:

部分接口訪問時,響應未知用戶,須要用session關聯接口,先調用登陸接口,把登陸接口的調用封裝成了一個實例方法,實現了複用,登陸以後,登陸接口的http響應會把session以 cookie的形式set到客戶端,以後的接口都會使用此session去請求

封裝登陸接口user_login.py

#coding:utf-8
import requests
from common.logger import Log

class Login():
    log = Log()

    def __init__(self,s):
        self.s = s

    def login(self,code,passwd):
        url = "http://192.168.20.100:8081/backend/system/user/login"
        headers = {"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",
                   "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36",
                   "X-Requested-With":"XMLHttpRequest",
                   "Cookie":"JSESSIONID=92D7FB4C7FB917B7D2E8DC429A63443F",
                   "Connection":"keep-alive"
                  }
        d = {"code":code,"passwd":passwd}

        res = self.s.post(url,headers=headers,data=d)
        result1 = res.text #字節輸出
        self.log.info(u"調用登陸方法,獲取結果:%s"%result1)
        return res.json()

優化api_test.py中部分代碼(紅色部分爲優化的代碼)

1.在請求接口前首先調用登陸接口

2.加入執行用例的編號(p),每循環一次自增1

#coding:utf-8

import xlrd,os
import requests
from datetime import datetime
from xlrd import xldate_as_tuple
from config import readConfig
from common.logger import Log
from case.user_login import Login

'''
獲取測試用例data所在的目錄
'''
d = os.path.dirname(__file__) #返回當前文件所在目錄(common文件夾路徑)
parent_path = os.path.dirname(d) #返回common的父級目錄
data_path = os.path.join(parent_path,'data') #返回data所在目錄
data_path1 = os.listdir(data_path) #返回data目錄下全部的文件

s = requests.session()
lon = Login(s)
log = Log()


def api_data():
    p = 1
    for filename in data_path1:
        book = xlrd.open_workbook(os.path.join(data_path,filename))

        '''
        獲取excel文件中接口信息
        '''
        table = book.sheet_by_index(0) #經過索引,獲取相應的列表,這裏表示獲取excel的第一個列表
        inf_name = table.row_values(1)[0] #返回接口名稱
        inf_address = table.row_values(1)[1] #返回接口地址
        inf_mode = table.row_values(1)[2] #返回請求方式

        '''
        獲取excel文件中測試用例信息
        '''
        sheet = book.sheet_by_index(1) #經過索引,獲取相應的列表,這裏表示獲取excel的第二個列表
        nrows = sheet.nrows #獲取全部行數
        filed = sheet.row_values(0)
        # print(filed)

        for i in range(1,nrows):
            d1 = {}
            for j in range(0,len(filed)-2):
                ctype = sheet.cell(i, j).ctype  # 表格的數據類型
                cell = sheet.cell_value(i, j)
                d = {}
                if ctype == 2 and cell % 1 == 0:  # 若是是整形
                    cell = int(cell)
                elif ctype == 3:
                    # 轉成datetime對象
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime('%Y/%m/%d')
                elif ctype == 4:
                    cell = True if cell == 1 else False
                # print(cell)
                d.update({filed[j]:cell})
                # print(d)
                d1.update(d)
            # print(d1)

            '''
            獲取excel文件中測試用例預期結果和描述
            '''
            a = []
            for k in range(len(filed)-2,len(filed)):
                ctype = sheet.cell(i, k).ctype  # 表格的數據類型
                cell = sheet.cell_value(i, k)
                if ctype == 2 and cell % 1 == 0:  # 若是是整形
                    cell = int(cell)
                elif ctype == 3:
                    # 轉成datetime對象
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime('%Y/%m/%d')
                elif ctype == 4:
                    cell = True if cell == 1 else False
                a.append(cell)
            # print(a[0])
            # print(type(a[0]))

            '''
            獲取cfg.ini配置文件中接口公共信息(ip和port)
            '''
            ip = readConfig.ip  # 獲取配置文件中接口ip
            i_port = readConfig.i_port  # 獲取配置文件中接口port
            url = "http://" + ip + ":" + i_port + inf_address
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
                "X-Requested-With": "XMLHttpRequest",
                "Connection": "keep-alive"
            }
            par = d1

            '''
            判斷請求方式是GET仍是POST,而且判斷測試用例預期結果與實際響應一致,全部接口請求前先調用登陸接口
            '''
            code = "xxx"  #登陸接口用戶code
            passwd = "xxx" #登陸接口用戶passwd
            lon.login(code, passwd)

            if inf_mode == 'GET':
                r = s.get(url, params=par)
                result = r.json()
                log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s"%(p,inf_name,a[1],str(result).replace('None','null')))
                # print(inf_name, str(result).replace('None','null'), a[1])
                if str(result).replace('None','null') == a[0]:
                    log.info("pass")
                    log.info("--------")
                else:
                    log.info("false")
                    log.info("--------")
            elif inf_mode == 'POST':
                r = s.post(url, data=par, headers=headers)
                result = r.json()
                log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s" % (p, inf_name,a[1],str(result).replace('None', 'null')))
                # print(inf_name, str(result).replace('None', 'null'), a[1])
                if str(result).replace('None', 'null') == a[0]:
                    log.info("pass")
                    log.info("--------")
                else:
                    log.info("false")
                    log.info("--------")

            p=p+1

api_data()

執行結果:

 

優化二:

excel中添加結果列,將每條用例的執行結果寫入excel中,由於excel版本是2007以上,採用openpyxl模塊去修改excel單元格的值,執行經過用綠色字體標註pass,執行不經過的用例紅色字體標註false

優化api_test.py中部分代碼

#coding:utf-8

import xlrd,os
import requests
import openpyxl
from openpyxl.styles import Font
# from xlutils.copy import copy
from datetime import datetime
from xlrd import xldate_as_tuple
from config import readConfig
from common.logger import Log
from case.user_login import Login


'''
獲取測試用例data所在的目錄
'''
d = os.path.dirname(__file__) #返回當前文件所在目錄(common文件夾路徑)
parent_path = os.path.dirname(d) #返回common的父級目錄
data_path = os.path.join(parent_path,'data') #返回data所在目錄
data_path1 = os.listdir(data_path) #返回data目錄下全部的文件

s = requests.session()
lon = Login(s)
log = Log()


def api_data():
    p = 1


    for filename in data_path1:
        book = xlrd.open_workbook(os.path.join(data_path,filename))

        '''
        使用xlwt操做excel,xlwt只支持excel2007如下版本
        '''

        # wb = copy(book)
        # ws = wb.get_sheet(1)

        '''
        使用openpyxl操做excel,openpyxl支持excel2007以上版本
        '''
        wb = openpyxl.load_workbook(os.path.join(data_path,filename))
        ws = wb.worksheets[1]
        font_green = Font(color="37b400")
        font_red = Font(color="ff0000")


        '''
        獲取excel文件中接口信息
        '''
        table = book.sheet_by_index(0) #經過索引,獲取相應的列表,這裏表示獲取excel的第一個列表
        inf_name = table.row_values(1)[0] #返回接口名稱
        inf_address = table.row_values(1)[1] #返回接口地址
        inf_mode = table.row_values(1)[2] #返回請求方式

        '''
        獲取excel文件中測試用例信息
        '''
        sheet = book.sheet_by_index(1) #經過索引,獲取相應的列表,這裏表示獲取excel的第二個列表
        nrows = sheet.nrows #獲取全部行數
        ncols = sheet.ncols #獲取全部列數
        filed = sheet.row_values(0)
        # print(filed)

        for i in range(1,nrows):
            d1 = {}
            for j in range(0,len(filed)-3):
                ctype = sheet.cell(i, j).ctype  # 表格的數據類型
                cell = sheet.cell_value(i, j)
                d = {}
                if ctype == 2 and cell % 1 == 0:  # 若是是整形
                    cell = int(cell)
                elif ctype == 3:
                    # 轉成datetime對象
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime('%Y/%m/%d')
                elif ctype == 4:
                    cell = True if cell == 1 else False
                # print(cell)
                d.update({filed[j]:cell})
                # print(d)
                d1.update(d)
            # print(d1)

            '''
            獲取excel文件中測試用例預期結果和描述
            '''
            a = []
            for k in range(len(filed)-3,len(filed)-1):
                ctype = sheet.cell(i, k).ctype  # 表格的數據類型
                cell = sheet.cell_value(i, k)
                if ctype == 2 and cell % 1 == 0:  # 若是是整形
                    cell = int(cell)
                elif ctype == 3:
                    # 轉成datetime對象
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime('%Y/%m/%d')
                elif ctype == 4:
                    cell = True if cell == 1 else False
                a.append(cell)
            # print(a[0])
            # print(type(a[0]))

            '''
            獲取cfg.ini配置文件中接口公共信息(ip和port)
            '''
            ip = readConfig.ip  # 獲取配置文件中接口ip
            i_port = readConfig.i_port  # 獲取配置文件中接口port
            url = "http://" + ip + ":" + i_port + inf_address
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
                "X-Requested-With": "XMLHttpRequest",
                "Connection": "keep-alive"
            }
            par = d1

            '''
            判斷請求方式是GET仍是POST,而且判斷測試用例預期結果與實際響應一致,全部接口請求前先調用登陸接口
            '''
            code = "xuxingan"
            passwd = "admin"
            lon.login(code, passwd)



            if inf_mode == 'GET':
                r = s.get(url, params=par)
                result = r.json()
                log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s"%(p,inf_name,a[1],str(result).replace('None','null')))
                # print(inf_name, str(result).replace('None','null'), a[1])
                if str(result).replace('None','null') == a[0]:
                    # ws.write(i,ncols-1,'pass'.encode('utf-8'))
                    ws.cell(row=i+1,column=ncols,value='pass').font = font_green
                    log.info("pass")
                    log.info("--------")
                else:
                    # ws.write(i,ncols-1,'false'.encode('utf-8'))
                    ws.cell(row=i+1, column=ncols, value='false').font = font_red
                    log.info("false")
                    log.info("--------")
            elif inf_mode == 'POST':
                r = s.post(url, data=par, headers=headers)
                result = r.json()
                log.info("編號:%s,接口名稱:%s,測試點:%s,響應:%s" % (p, inf_name,a[1],str(result).replace('None', 'null')))
                # print(inf_name, str(result).replace('None', 'null'), a[1])
                if str(result).replace('None', 'null') == a[0]:
                    # ws.write(i,ncols-1,'pass'.encode('utf-8'))
                    ws.cell(row=i+1, column=ncols, value='pass').font = font_green
                    log.info("pass")
                    log.info("--------")
                else:
                    # ws.write(i,ncols-1,'false'.encode('utf-8'))
                    ws.cell(row=i+1, column=ncols, value='false').font = font_red
                    log.info("false")
                    log.info("--------")
            wb.save(os.path.join(data_path, filename))
            p=p+1


    log.info("總計%s條用例"%p)

api_data()

執行結果

 

總結:

第一個版本有點粗糙,1.後續加入將每條用例的執行結果寫入測試用例excel文件 2.生成自動化測試報告

相關文章
相關標籤/搜索