Python實現http接口自動化測試

網上http接口自動化測試Python實現有很多,我也是在慕課網上學習了相關課程,並實際操作了一遍,於是進行一些總結,便於以後回顧溫習,有許多不完善的地方,希望大神們多多指教!

接口測試常用的工具有fiddler,postman,jmeter等,使用這些工具測試時,需要了解常用的接口類型和區別,比如我用到的post和get請求,表面上看get用於獲取數據post用於修改數據,兩者傳遞參數的方式也有不一樣,get是直接在url裏通過?來連接參數,而post則是把數據放在HTTP的包體內(request body),兩者的本質就是TCP鏈接,並無差別,但是由於HTTP的規定和瀏覽器/服務器的限制,導致他們在應用過程中體現出一些不同。具體的可以參考此博文,講解的比較通俗易懂。這些在工具中可以直接選擇,python需要藉助requests包。

確定好接口類型後,需要做的就是準備測試數據和設計測試用例了,測試用例比如說可以判斷返回狀態響應碼,或者對返回數據進行判別等,具體可以參考postman中的echo.collections,對於python可以用unittest來組織測試用例和添加斷言進行判斷。而對於測試數據的準備,需要做到數據和業務儘量分離,即將測試數據參數化,在工具中可以通過添加變量的形式實現,對於python設計到的有關包有xlrd,json,如果需要連接數據庫還需要mysql。
測試完成後生產報告或者發送郵件,也可以使用HTMLTestRunner和smtplib等。
我也從這三大方面進行總結:

1. 接口方法實現和封裝

requests庫可以很好的幫助我們實現HTTP請求,API參考文檔,這裏我創建了runmethod.py,裏面包含RunMethod類:
這裏寫圖片描述
這裏需要注意就是python默認參數和可選參數要放在必選參數後面,對於相應數據使用json格式進行返回。參數verify=false表示忽略對 SSL 證書的驗證。

2.組織測試和生成報告

使用unittest來組織測試、添加測試用例和斷言,測試報告可以下載HTMLTestRunner.py並放在python安裝路徑lib下即可,代碼如下:

#coding:utf-8
import unittest
import json
import HTMLTestRunner
from mock import mock
#from demo import RunMain
from runmethod import RunMethod
from mock_demo import mock_test
import os
class TestMethod(unittest.TestCase):
    def setUp(self):
        #self.run=RunMain()
        self.run = RunMethod()
    def test_01(self):
        url = 'http://coding.imooc.com/api/cate'
        data = {
            'timestamp':'1507034803124',
            'uid':'5249191',
            'uuid':'5ae7d1a22c82fb89c78f603420870ad7',
            'secrect':'078474b41dd37ddd5efeb04aa591ec12',
            'token':'7d6f14f21ec96d755de41e6c076758dd',
            'cid':'0',
            'errorCode':1001
        }
        #self.run.run_main = mock.Mock(return_value=data)
        res = mock_test(self.run.run_main,data,url,"POST",data)
        #res = self.run.run_main(url,'POST',data)
        print(res)
        self.assertEqual(res['errorCode'],1001,"測試失敗")


    @unittest.skip('test_02')   
    def test_02(self):

        url = 'http://coding.imooc.com/api/cate'
        data = {
            'timestamp':'1507034803124',
            'uid':'5249191',
            'uuid':'5ae7d1a22c82fb89c78f603420870ad7',
            'secrect':'078474b41dd37ddd5efeb04aa591ec12',
            'token':'7d6f14f21ec96d755de41e6c076758dd',
            'cid':'0'

        }

        res = self.run.run_main(url,'GET',data)
        self.assertEqual(res['errorCode'],1006,"測試失敗")

    def test_03(self):
        url = 'http://coding.imooc.com/api/cate'
        data = {
            'timestamp':'1507034803124',
            'uid':'5249191',
            'uuid':'5ae7d1a22c82fb89c78f603420870ad7',
            'secrect':'078474b41dd37ddd5efeb04aa591ec12',
            'token':'7d6f14f21ec96d755de41e6c076758dd',
            'cid':'0',
            'status':11
            }

        res = mock_test(self.run.run_main,data,url,'GET',data)
        print(res)
        self.assertGreater(res['status'],10,'測試通過')

if __name__ == '__main__':

    filepath = os.getcwd()+'\\report.html'
    fp = open(filepath,'wb+')
    suite = unittest.TestSuite()
    suite.addTest(TestMethod('test_01'))
    suite.addTest(TestMethod('test_02'))
    suite.addTest(TestMethod('test_03'))
    runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title='this is demo test')
    runner.run(suite)
    #unittest.main()

這裏setUp()方法用來在測試之前執行,同樣的有tearDown()方法,測試case以test開頭進行編寫,然後使用TestSuit類生成測試套件,將case添加進去,運行run suite即可。當測試用例較多時,可以生成多個測試類別,然後使用TestLoader().LoadTestsFromTestCase(測試類)生成測試用例,再加入testsuite執行。
在這裏,我使用了學習到的mock方法,mock即模擬數據,當我們無法實際執行獲得數據時可以使用mock方法,模擬生成我們需要判別的數據,這裏mock_test方法同樣進行了封裝:

#coding:utf-8
from mock import mock
def mock_test(mock_method,request_data,url,method,response_data):
    mock_method = mock.Mock(return_value=response_data)
    res = mock_method(url,method,request_data)
    return res

這裏模擬的是self.run.run_main()方法,將這個方法的返回值設爲response_data,而最終我們要判斷的是返回值res,可以結合test_02對比,

res = self.run.run_main(url,'GET',data)

所以又需要傳入參數url,method,request_data,最後返回相應數據即可,

res = mock_test(self.run.run_main,data,url,'GET',data)

這裏我假設返回的數據爲data,隨意添加了幾個判斷條件errorCode==1001status>10作爲判斷依據。最後生成報告如下:
這裏寫圖片描述

3 測試數據處理

這一部分主要包括設計測試數據,數據提取和參數化,以及解決數據依賴。這裏還是以慕課網上學習的例子爲例,主要依據測試目的和使用流程來設計,如下圖:
這裏寫圖片描述
這裏首先涉及到的就是對Excel表格的操作,導入相關庫import xlrd,先對如上表的測試用例進行配置文件編寫:

class global_var:
    Id = '0'
    request_name = '1'
    url = '2'
    run = '3'
    request_way = '4'
    header = '5'
    case_depend = '6'
    data_depend = '7'
    field_depend = '8'
    data = '9'
    expect = '10'
    result = '11'

再定義返回該列的函數,例如獲取caseId和URL:

def get_id():
    return global_var.Id
def get_url():
    return global_var.url

3.1操作Excel文件

然後我們再編寫操作Excel的模塊,主要包含了對Excel表格的操作,獲取表單、行、列、單元格內容等。

import xlrd
from xlutils.copy import copy
class OperationExcel:
    def __init__(self,file_name=None,sheet_id=None):
        if file_name:
            self.file_name = file_name
            self.sheet_id = sheet_id    
        else:
            self.file_name = '/dataconfig/case1.xls'
            self.sheet_id = 0
        self.data = self.get_data()
    #獲取sheets的內容
    def get_data(self):
        data = xlrd.open_workbook(self.file_name)
        tables = data.sheets()[self.sheet_id]
        return tables
    #獲取單元格的行數
    def get_lines(self):
        tables = self.data
        return tables.nrows
    #獲取某一個單元格的內容
    def get_cell_value(self,row,col):
        return self.data.cell_value(row,col)
    #寫入數據
    def write_value(self,row,col,value):
        '''寫入excel數據row,col,value'''
        read_data = xlrd.open_workbook(self.file_name)
        write_data = copy(read_data)
        sheet_data = write_data.get_sheet(0)
        sheet_data.write(row,col,value)
        write_data.save(self.file_name)

其中寫數據用於將運行結果寫入Excel文件,先用copy複製整個文件,通過get_sheet()獲取的sheet有write()方法。

3.2操作json文件

對於請求數據,我是根據關鍵字從json文件裏取出字段,所以還需要json格式的數據文件,如下。對應請求數據中的各個關鍵字:
這裏寫圖片描述
所以還需要編寫對應操作json文件的模塊:

import json
class OperetionJson:
    def __init__(self,file_path=None):
        if file_path  == None:
            self.file_path = '/dataconfig/user.json'
        else:
            self.file_path = file_path
        self.data = self.read_data()
    #讀取json文件
    def read_data(self):
        with open(self.file_path) as fp:
            data = json.load(fp)
            return data
    #根據關鍵字獲取數據
    def get_data(self,id):
        print(type(self.data))
        return self.data[id]

讀寫操作使用的是json.load(),json.dump() 傳入的是文件句柄。

3.3 獲得測試數據

在定義好Excel和json操作模塊後,我們將其應用於我們的測試表單,定義一個獲取數據模塊:

from util.operation_excel import OperationExcel
import data.data_config
from util.operation_json import OperetionJson
class GetData:
    def __init__(self):
        self.opera_excel = OperationExcel()
    #去獲取excel行數,就是我們的case個數 
    def get_case_lines(self):
        return self.opera_excel.get_lines()
    #獲取是否執行
    def get_is_run(self,row):
        flag = None
        col = int(data_config.get_run())
        run_model = self.opera_excel.get_cell_value(row,col)
        if run_model == 'yes':
            flag = True
        else:
            flag = False
        return flag
    #是否攜帶header
    def is_header(self,row):
        col = int(data_config.get_header())
        header = self.opera_excel.get_cell_value(row,col)
        if header != '':
            return header
        else:
            return None
    #獲取請求方式
    def get_request_method(self,row):
        col = int(data_config.get_run_way())
        request_method = self.opera_excel.get_cell_value(row,col)
        return request_method
    #獲取url
    def get_request_url(self,row):
        col = int(data_config.get_url())
        url = self.opera_excel.get_cell_value(row,col)
        return url
    #獲取請求數據
    def get_request_data(self,row):
        col = int(data_config.get_data())
        data = self.opera_excel.get_cell_value(row,col)
        if data == '':
            return None
        return data
    #通過獲取關鍵字拿到data數據
    def get_data_for_json(self,row):
        opera_json = OperetionJson()
        request_data = opera_json.get_data(self.get_request_data(row))
        return request_data
    #獲取預期結果
    def get_expcet_data(self,row):
        col = int(data_config.get_expect())
        expect = self.opera_excel.get_cell_value(row,col)
        if expect == '':
            return None
        return expect
    def write_result(self,row,value):
        col = int(data_config.get_result())
        self.opera_excel.write_value(row,col,value)

該模塊將Excel操作類實例化後用於操作測試表單,分別獲得測試運行所需的各種條件。

3.4 判斷條件

這裏判斷一個case是否通過,是將實際結果和預期結果進行對比,比如,狀態碼status是不是200,或者在返回數據中查看是否含有某一字段:

import json
class CommonUtil:
    def is_contain(self,str_one,str_two):
        '''
        判斷一個字符串是否再另外一個字符串中
        str_one:查找的字符串
        str_two:被查找的字符串
        '''
        flag = None
        if isinstance(str_one,unicode):
            str_one = str_one.encode('unicode-escape').decode('string_escape')
        return cmp(str_one,str_two)
        if str_one in str_two:
            flag = True
        else:
            flag = False
        return flag

所以我們獲得expec數據和相應數據,再調用這個類別的is_contain() 方法就能判斷。

3.5 數據依賴問題

當我們要執行的某個case的相應數據依賴於前面某個case的返回數據時,我們需要對相應數據進行更新,比如case12的相應數據request_data[數據依賴字段]的值應該更新於case11的返回數據response_data[依賴的返回字段] 。那麼我們就需要先執行case11拿到返回數據,再寫入case12的相應數據,首先對操作Excel的模塊進行更新加入:

    #獲取某一列的內容
    def get_cols_data(self,col_id=None):
        if col_id != None:
            cols = self.data.col_values(col_id)
        else:
            cols = self.data.col_values(0)
        return cols
    #根據對應的caseid找到對應的行號
    def get_row_num(self,case_id):
        num = 0
        cols_data = self.get_cols_data()
        for col_data in cols_data:
            if case_id in col_data:
                return num
            num = num+1
    #根據行號,找到該行的內容
    def get_row_values(self,row):
        tables = self.data
        row_data = tables.row_values(row)
        return row_data
    #根據對應的caseid 找到對應行的內容
    def get_rows_data(self,case_id):
        row_num = self.get_row_num(case_id)
        rows_data = self.get_row_values(row_num)
        return rows_data

即我們通過依賴的caseId找到對應的行號,拿到整行的內容。我們默認拿到列0的內容(即caseId)循環整列找到依賴的caseId在第幾行,然後返回整行數據,即實現方法get_rows_data(case_id) 。然後再去執行和更新,我們編寫一個專門處理依賴數據的模塊,同時,爲了獲取依賴數據,還需要對獲取數據模塊進行更新如下:

    #獲取依賴數據的key
    def get_depend_key(self,row):
        col = int(data_config.get_data_depend())
        depent_key = self.opera_excel.get_cell_value(row,col)
        if depent_key == "":
            return None
        else:
            return depent_key
    #判斷是否有case依賴
    def is_depend(self,row):
        col = int(data_config.get_case_depend())
        depend_case_id = self.opera_excel.get_cell_value(row,col)
        if depend_case_id == "":
            return None
        else:
            return depend_case_id
    #獲取數據依賴字段
    def get_depend_field(self,row):
        col = int(data_config.get_field_depend())
        data = self.opera_excel.get_cell_value(row,col)
        if data == "":
            return None
        else:
            return data

將方法應用於專門處理依賴數據的模塊:

from util.operation_excel import OperationExcel
from base.runmethod import RunMethod
from data.get_data import GetData
from jsonpath_rw import jsonpath,parse
class DependdentData:
    def __init__(self,case_id):
        self.case_id = case_id
        self.opera_excel = OperationExcel()
        self.data = GetData()
    #通過case_id去獲取該case_id的整行數據
    def get_case_line_data(self):
        rows_data = self.opera_excel.get_rows_data(self.case_id)
        return rows_data
    #執行依賴測試,獲取結果
    def run_dependent(self):
        run_method = RunMethod()
        row_num  = self.opera_excel.get_row_num(self.case_id)
        request_data = self.data.get_data_for_json(row_num)
        #header = self.data.is_header(row_num)
        method = self.data.get_request_method(row_num)
        url = self.data.get_request_url(row_num)
        res = run_method.run_main(method,url,request_data)
        return json.loads(res)#返回數據是字符串需要轉成json格式方便後續查詢
    #根據依賴的key去獲取執行依賴測試case的響應,然後返回
    def get_data_for_key(self,row):
        depend_data = self.data.get_depend_key(row)
        response_data = self.run_dependent()
        json_exe = parse(depend_data)
        madle = json_exe.find(response_data)
        return [math.value for math in madle][0]

其中jsonpath用於找到多層級數據,類似於xpath,即通過依賴字段表示的層級關係在返回數據中找到對應的值,最後再執行該case時把數據更新。

3.6 主流程

把上述所有模塊導入,編寫主流程模塊:

from base.runmethod import RunMethod
from data.get_data import GetData
from util.common_util import CommonUtil
from data.dependent_data import DependdentData
from util.operation_header import OperationHeader
from util.operation_json import OperetionJson
class RunTest:
    def __init__(self):
        self.run_method = RunMethod()
        self.data = GetData()
        self.com_util = CommonUtil()
    #程序執行
    def go_on_run(self):
        res = None
        pass_count = []
        fail_count = []
        rows_count = self.data.get_case_lines()
        for i in range(1,rows_count):
            is_run = self.data.get_is_run(i)
            if is_run:
                url = self.data.get_request_url(i)
                method = self.data.get_request_method(i)
                request_data = self.data.get_data_for_json(i)
                expect = self.data.get_expcet_data_for_mysql(i)
                header = self.data.is_header(i)
                depend_case = self.data.is_depend(i)
                if depend_case != None:
                    self.depend_data = DependdentData(depend_case)
                    #獲取的依賴響應數據
                    depend_response_data = self.depend_data.get_data_for_key(i)
                    #獲取依賴的key
                    depend_key = self.data.get_depend_field(i)
                    request_data[depend_key] = depend_response_data
                res = self.run_method.run_main(method,url,request_data)
                if self.com_util.is_contain(expect,res):
                    self.data.write_result(i,'pass')
                    pass_count.append(i)
                else:
                    self.data.write_result(i,res)
                    fail_count.append(i)
        print(len(pass_count))
        print(len(fail_count))
if __name__ == '__main__':
    run = RunTest()
    run.go_on_run()

這樣我們就完成了測試執行,並對結果進行了統計,同時解決了數據依賴問題。


新手學習,歡迎指教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章