python3+requests:接口自動化測試(二)

轉載請註明出處:http://www.javashuo.com/article/p-amamsfcw-c.htmlhtml

 

前言:上篇文章python3+requests+unittest:接口自動化測試(一):http://www.javashuo.com/article/p-yiygkcyw-db.html ,已經介紹了基於unittest框架的實現接口自動化,可是也存在一些問題,好比最明顯的測試數據和業務沒有區分開,接口用例不便於管理等,因此又對此修改完善。接下來主要是介紹該套接口自動化框架的設計到實現,參考代碼的git地址:https://github.com/zhangying123456/python3_interfacepython

 

1.代碼框架展現git

 

(1)case:存放測試用例數據的,好比請求類型get/post、請求url、請求header、請求數據等;github

(2)data:獲取excel文件中相應數據的方法封裝,獲取excel中對應表格內的數據,excel的行列數據等:get_data.py;判斷用例之間是否存在依賴關係並獲取依賴數據:dependent_data.py;初始化excel文件:data_config.py;json

(3)dataconfig:存放請求中涉及到的header、data、cookies等數據;服務器

(4)log:存放測試完成以後生成的日誌文件,能夠查看日誌定位問題;cookie

(5)main:腳本執行的主函數run_test.pysession

(6)util:通用方法的封裝,各類不一樣斷言方式common_assert.py;對excel文件的讀寫操做operation_excel.py;從請求返回數據中拿取數據做爲下一個接口的請求header數據operation_header.py;從json文件中拿取想要的數據operation_json.py;將接口自動化過程當中的相關日誌輸出到log.txt中print_log.py;根據請求類型的不一樣執行對應的get/post方法runmethod.py;將測試結果以郵件形式發送給相關人員send_mail.py。app

 

2.代碼實現說明框架

(1)首先看下用例數據

說明:該用例只是用來覆蓋一些接口場景而測試使用的,有興趣的能夠參考源碼用本身項目的真實數據來實現

 

 先判斷是否執行:若是yes,執行該條用例;若是no,直接跳過該條用例。

執行用例:獲取用例的url、請求類型、請求頭header、請求數據,request.get/post執行該條接口用例。

在執行用例過程當中,會存在特殊狀況:(1)好比test_04依賴於test_03,test_04中的請求字段supplier的參數數據來源於test_03的response中value[0].biz字段的數據,因此在執行接口過程當中須要判斷是否存在依賴關係;(2)好比test_06請求數據須要test_05的response中的cookies數據,因此這種類型接口也要特殊處理。

執行完成後:寫入實際結果,與預期結果作對比,進行斷言。

(2)看了用例excel後,對基本的流程有個大概瞭解,如今的問題就是如何拿取對應的數據執行接口獲得運行結果

    if is_run:
        url = self.data.get_request_url(i)
        method = self.data.get_request_method(i)
        #獲取請求參數
        data = self.data.get_data_value(i)
        # 獲取excel文件中header關鍵字
        header_key = self.data.get_request_header(i)
        # 獲取json文件中header_key對應的頭文件數據
        header = self.data.get_header_value(i)
        expect = self.data.get_expect_data(i)
        depend_case = self.data.is_depend(i)

舉例說明1:請求url數據是存放在excel中,咱們經過操做excel文件到特定單元格拿到url數據

    #獲取url
    def get_request_url(self,row):
        col = int(data_config.get_url())
        url = self.oper_excel.get_cell_value(row,col)
        return url

舉例說明2:請求頭header或者請求數據中有的數據爲空,因此咱們在拿取數據過程當中要作判斷

    #獲取請求數據
    def get_request_data(self,row):
        col = int(data_config.get_data())
        data = self.oper_excel.get_cell_value(row,col)
        if data == '':
            return None
        return data

首先拿取excel中表格中的關鍵字,再經過關鍵字去對應json文件拿取具體的請求數據。好比先拿取excel中請求數據中的hotwords,再根據此關鍵字去json文件讀取hotwords的鍵值數據

    "hotwords": {
        "bizName": "globalSearchClient",
        "sign": "8c8bc3ee9d6c4b7b8a390ae298cb6db5",
        "timeMills": "1524906299999"
    }
    #經過獲取請求關鍵字拿到data數據
    def get_data_value(self,row):
        oper_json = OperationJson('../dataconfig/request_data.json')
        request_data = oper_json.get_data(self.get_request_data(row))
        return request_data
    #根據關鍵字獲取數據
    '''
    dict['key']只能獲取存在的值,若是不存在則觸發KeyError
    dict.get(key, default=None),返回指定鍵的值,若是值不在字典中返回默認值None
    excel文件中請求數據有可能爲空,因此用get方法獲取
    '''
    def get_data(self,key):
        # return self.data[key]
        return self.data.get(key)

 (3)通常的接口都是單接口,便是單獨請求,沒有上下依賴關係的,針對這種只要模擬請求拿到數據進行斷言就能夠了。可是實際項目中會存在特殊場景,好比test_03和test04

說明:test_04中,請求數據qqmusic_more中的supplier字段依賴於test_03中的返回數據value[0].biz的值

"qqmusic_more": {
        "bizName": "globalSearchClient",
        "appLan": "zh_CN",
        "musicLimit": "20",
        "imei": "864044030085594",
        "keyword": "fly",
        "timeMills": "1527134461256",
        "page": "0",
        "sign": "17daa7e3e84bd4dfbe9a1bd9a1bd7e62",
        "mac": "90f05205d7b7",
        "sessionId": "43e605b914874cd99b47ac997e19c1a1",
        "network": "1",
        "supplier": "",
        "language": "zh_CN",
    }

先執行test_03,獲取依賴的返回數據value[0].biz的值

    #執行依賴測試,獲取test_03返回結果
    def run_dependent(self):
        row_num = self.oper_excel.get_row_num(self.case_id)
        request_data = self.data.get_data_value(row_num)
        header = self.data.get_request_header(row_num)
        method = self.data.get_request_method(row_num)
        url = self.data.get_request_url(row_num)
        res = self.method.run_main(method,url,request_data,header,params=request_data)
        return res

    #獲取依賴字段的響應數據:經過執行依賴測試case來獲取響應數據,響應中某個字段數據做爲依賴key的value
    def get_value_for_key(self,row):
        #獲取依賴的返回數據key
        depend_data = self.data.get_depend_key(row)
        print(depend_data)  #depend_data打印數據:value[0].biz
        #執行依賴case返回結果
        response_data = self.run_dependent()
        # print(depend_data)
        # print(response_data)

        return [match.value for match in parse(depend_data).find(response_data)][0]

再將value[0].biz值放入test_04請求數據qqmusic_more中的supplier字段中

        if depend_case != None:
            self.depend_data = DependentData(depend_case)
            #獲取依賴字段的響應數據
            depend_response_data = self.depend_data.get_value_for_key(i)
            #獲取請求依賴的key
            depend_key = self.data.get_depend_field(i)
            #將依賴case的響應返回中某個字段的value賦值給該接口請求中某個參數
            data[depend_key] = depend_response_data

(4)拿到請求相關數據後,執行該條case,獲取response;而後實際結果與預期結果進行斷言

res = self.run_method.run_main(method,url,data,header,params=data)
'''
get請求參數是params:request.get(url='',params={}),post請求數據是data:request.post(url='',data={})
excel文件中沒有區分直接用請求數據表示,則data = self.data.get_data_value(i)拿到的數據,post請求就是data=data,get請就是params=data
'''

根據get、post類型區分

class RunMethod:
    def post_main(self,url,data,header=None):
        res = None
        if header != None:
            res = requests.post(url=url,data=data,headers=header)
        else:
            res = requests.post(url=url,data=data)
        return res.json()

    def get_main(self,url,params=None,header=None):
        res = None
        if header != None:
            res = requests.get(url=url, params=params, headers=header)
        else:
            res = requests.get(url=url, params=params)
        return res.json()

    def run_main(self,method,url,data=None,header=None,params=None):
        res = None
        if method == 'post':
            res = self.post_main(url,data,header)
        else:
            res = self.get_main(url,params,header)
        return res

(5)執行接口case過程當中,可能存在某條case異常報錯,致使下面的case沒法運行,因此咱們既要將異常日誌存放在特定文件中方便後續排查,也要保證下面的case可以不受影響繼續執行完

           try:...
            
           except Exception as e:
                # 將報錯寫入指定路徑的日誌文件裏
                with open(log_file,'a',encoding='utf-8') as f:
                    f.write("\n第%s條用例報錯:\n" % i)
                initLogging(log_file,e)
                fail_count.append(i)

抓取日誌的方法可使用python內置模塊logging,具體用法能夠參考:http://www.javashuo.com/article/p-khatqxur-dn.html

import logging

def initLogging(logFilename,e):

  logging.basicConfig(
                    level = logging.INFO,
                    format ='%(asctime)s-%(levelname)s-%(message)s',
                    datefmt = '%y-%m-%d %H:%M',
                    filename = logFilename,
                    filemode = 'a')
  fh = logging.FileHandler(logFilename,encoding='utf-8')
  logging.getLogger().addHandler(fh)
  log = logging.exception(e)
  return log

日誌文件log.txt結果:直接定位問題出在哪兒

第5條用例報錯:
18-06-19 10:27-ERROR-string indices must be integers
Traceback (most recent call last):
  File "C:/Users/xxx/Documents/GitHub/python3_interface/main/run_test.py", line 70, in go_on_run
    op_header.write_cookie()
  File "C:\Users\xxx\Documents\GitHub\python3_interface\util\operation_header.py", line 30, in write_cookie
    cookie = requests.utils.dict_from_cookiejar(self.get_cookie())
  File "C:\Users\zhangying1\Documents\GitHub\python3_interface\util\operation_header.py", line 25, in get_cookie
    url = self.get_response_url()+"&callback=jQuery21008240514814031887_1508666806688&_=1508666806689"
  File "C:\Users\xxx\Documents\GitHub\python3_interface\util\operation_header.py", line 18, in get_response_url
    url = self.response['data']['url'][0]
TypeError: string indices must be integers

(6)接口自動化測試執行完成後,須要將測試結果發送給項目組相關人員,郵件發送實現方法參考:http://www.javashuo.com/article/p-rmamgiew-ec.html

self.send_mail.send_main(pass_count,fail_count,no_run_count)
#coding:utf-8
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import datetime

class SendEmail:
    global send_user
    global email_host
    global password
    password = "lunkbrgwqxhfjgxx"
    email_host = "smtp.qq.com"
    send_user = "xxx@qq.com"

    def send_mail(self,user_list,sub,content):
        user = "shape" + "<" + send_user + ">"

        # 建立一個帶附件的實例
        message = MIMEMultipart()
        message['Subject'] = sub
        message['From'] = user
        message['To'] = ";".join(user_list)

        # 郵件正文內容
        message.attach(MIMEText(content, 'plain', 'utf-8'))

        # 構造附件(附件爲txt格式的文本)
        filename = '../log/log.txt'
        time = datetime.date.today()
        att = MIMEText(open(filename, 'rb').read(), 'base64', 'utf-8')
        att["Content-Type"] = 'application/octet-stream'
        att["Content-Disposition"] = 'attachment; filename="%s_Log.txt"'% time
        message.attach(att)

        server = smtplib.SMTP_SSL()
        server.connect(email_host,465)# 啓用SSL發信, 端口通常是465
        # server.set_debuglevel(1)# 打印出和SMTP服務器交互的全部信息
        server.login(send_user,password)
        server.sendmail(user,user_list,message.as_string())
        server.close()

    def send_main(self,pass_list,fail_list,no_run_list):
        pass_num = len(pass_list)
        fail_num = len(fail_list)
        #未執行的用例
        no_run_num = len(no_run_list)
        count_num = pass_num + fail_num + no_run_num

        #成功率、失敗率
        '''
        用%對字符串進行格式化
        %d 格式化整數
        %f 格式化小數;想保留兩位小數,須要在f前面加上條件:%.2f;用%%來表示一個%
        若是你不太肯定應該用什麼,%s永遠起做用,它會把任何數據類型轉換爲字符串 
       '''
        pass_result = "%.2f%%" % (pass_num/count_num*100)
        fail_result = "%.2f%%" % (fail_num/count_num*100)
        no_run_result = "%.2f%%" % (no_run_num/count_num*100)

        user_list = ['xxx@qq.com']
        sub = "接口自動化測試報告"
        content = "接口自動化測試結果:\n經過個數%s個,失敗個數%s個,未執行個數%s個:經過率爲%s,失敗率爲%s,未執行率爲%s\n日誌見附件" % (pass_num,fail_num,no_run_num,pass_result,fail_result,no_run_result)
        self.send_mail(user_list,sub,content)

到此,就基本完成。

 

說明:

1.只是大概整理了接口自動化實現的設計流程,須要源碼參考的可查看:https://github.com/zhangying123456/python3_interface

2.這套接口框架中還有不少須要完善的地方,好比斷言方法不夠豐富,好比測試報告展現須要完善,等等。各位有興趣的能夠不斷完善改進

相關文章
相關標籤/搜索