基于python+requests+unittest實(shí)現(xiàn)的接口自動(dòng)化測(cè)試方案

第一篇簡(jiǎn)書(shū)文章獻(xiàn)給你误墓,筆芯~

背景簡(jiǎn)介

最近在做接口測(cè)試俭厚,每次新功能提交測(cè)試,有大量的接口歉秫,有些接口參數(shù)有幾十個(gè)籽暇,轉(zhuǎn)化成用例温治,除了每個(gè)參數(shù)基礎(chǔ)的邊界值、等價(jià)類(lèi)校驗(yàn)外图仓,還涉及很多業(yè)務(wù)邏輯的比對(duì)測(cè)試。由于團(tuán)隊(duì)提供的API接口在swagger上面但绕,每次測(cè)試在swagger上拼參數(shù)測(cè)試確實(shí)很方便救崔,但是沒(méi)有個(gè)記錄惶看,測(cè)試完了除非每個(gè)團(tuán)隊(duì)成員都對(duì)接口參數(shù)有保存,后續(xù)再要驗(yàn)證時(shí)要重新拼參數(shù)或是找同事拿已經(jīng)拼好的參數(shù)六孵,增加時(shí)間成本纬黎,而且要回歸驗(yàn)證有需要再重新調(diào)一次,考慮到這些劫窒,做了一些需求調(diào)研本今,決定使用python+requests+unittest實(shí)現(xiàn)讀取Excel用例自動(dòng)執(zhí)行請(qǐng)求。項(xiàng)目落成主巍,在此做個(gè)記錄冠息,當(dāng)然仍有許多不足之處需改進(jìn)。

需求分析

要做一個(gè)東西孕索,當(dāng)然是要先搞清楚需求逛艰,即我們的現(xiàn)狀,要實(shí)現(xiàn)怎樣的效果搞旭,搞清楚了要實(shí)現(xiàn)什么散怖,再來(lái)一步一步的任務(wù)拆分,然后想辦法實(shí)現(xiàn)肄渗。

首先镇眷,開(kāi)發(fā)提供給我們測(cè)試的接口是通過(guò)swagger自動(dòng)生成,沒(méi)有特定的接口文檔翎嫡,swagger上有每個(gè)接口詳細(xì)的參數(shù)描述等信息欠动。接口請(qǐng)求類(lèi)型主要有GET、POST钝的、PUT翁垂、DELETE,針對(duì)每個(gè)類(lèi)型的接口硝桩,傳參類(lèi)型有所不同沿猜,不能用通用的requests傳參來(lái)做拼接請(qǐng)求,如下:


params_type.png

如果類(lèi)型為URL Path的碗脊,做請(qǐng)求時(shí)就需要拿到base_url+URL Path啼肩,拼接好后作為請(qǐng)求的url;
如果類(lèi)型為Query的衙伶,就需要把參數(shù)以key-value的形式進(jìn)行傳參祈坠;等等...
依此,設(shè)計(jì)Excel模板如下:


Excel模板.png

每個(gè)團(tuán)隊(duì)成員只需要在Excel里面寫(xiě)一次用例矢劲,執(zhí)行的時(shí)候記錄下每個(gè)傳參赦拘、請(qǐng)求的基礎(chǔ)url,預(yù)期結(jié)果芬沉,之后就可以自動(dòng)回歸啦~

接下來(lái)就是要怎么實(shí)現(xiàn)這個(gè)過(guò)程了...

實(shí)現(xiàn)

就像把大象塞冰箱里面需要幾個(gè)步驟一樣躺同,打開(kāi)冰箱阁猜、把大象塞進(jìn)去、關(guān)上冰箱....(⊙﹏⊙)b....大象根本就塞不下一般的冰箱好不啦~

言歸正傳蹋艺,怎樣實(shí)現(xiàn)這個(gè)過(guò)程:

1.遍歷excel剃袍,讀取數(shù)據(jù)
2.處理讀取到的數(shù)據(jù),拿到我們想要的數(shù)據(jù)
3.拼接請(qǐng)求捎谨,發(fā)送request請(qǐng)求
4.拿到response狀態(tài)值民效,對(duì)比excel預(yù)期結(jié)果是否一致

架構(gòu)如圖(也說(shuō)不上什么架構(gòu)哈~)


項(xiàng)目結(jié)構(gòu).png

說(shuō)明:
·operate_excel.py:封裝一些操作excel數(shù)據(jù)表的方法
·get_data.py:封裝獲取excel值的方法
·data_config.py:配置excel的固定列,封裝獲取對(duì)應(yīng)列的方法
·handle_requests.py:封裝request請(qǐng)求
·run.py:主函數(shù)涛救,根據(jù)拿到的請(qǐng)求類(lèi)型判斷畏邢,拼裝請(qǐng)求url及參數(shù)自動(dòng)發(fā)送請(qǐng)求,返回實(shí)際請(qǐng)求的status_code以及excel的expect州叠。然后作為參數(shù)傳遞給unittest的test方法自動(dòng)斷言

data_config.py:

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
獲取Excel每列
"""

class global_var:
    Id = '0'
    interface_name = '1'
    case_name = '2'
    case_paramas_type = '3'
    case_method = '4'
    case_headers = '5'
    parameters = '6'
    query = '7'
    body = '8'
    request_url ='9'
    expect = '10'
    result = '11'
    sql1 = '12'
    sql2 = '13'

def get_id():
    return global_var.Id

def get_url():
    return global_var.request_url

def get_interface_name():
    return global_var.interface_name

def get_case_name():
    return global_var.case_name

def get_case_params_type():
    return global_var.case_paramas_type

def get_method():
    return global_var.case_method

def get_headers():
    return global_var.case_headers

def get_parameters():
    return global_var.parameters

def get_query():
    return global_var.query

def get_body():
    return global_var.body

def get_expect():
    return global_var.expect

get_data.py:

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
獲取單元格中的內(nèi)容
"""

from NewTest.public.operate_excel import OperateExcel
from NewTest.public import data_config
import xlrd

class GetData:

    def __init__(self,file_name):
        self.opera_excel = OperateExcel(file_name)

    # 獲取Excel行數(shù)棵红,即case個(gè)數(shù)
    def get_case_linese(self):
        return self.opera_excel.get_lines()

    # 獲取是否攜帶headers
    def is_header(self,row):
        col = int(data_config.get_headers())
        header = self.opera_excel.get_cell_value(row,col)
        if header != '':
            return header
        else:
            return None

    # 獲取請(qǐng)求方式
    def get_request_method(self,row):
        col = int(data_config.get_method())
        request_method = self.opera_excel.get_cell_value(row,col)
        return request_method

    # 獲取請(qǐng)求url
    def get_request_url(self,row):
        col = int(data_config.get_url())
        request_url = self.opera_excel.get_cell_value(row,col)
        if request_url == '':
            return None
        else:
            return request_url

    # 獲取請(qǐng)求query
    def get_request_query(self,row):
        col = int(data_config.get_query())
        reuqest_query = self.opera_excel.get_cell_value(row,col)
        if reuqest_query == '':
            return None
        else:
            return reuqest_query

    # 獲取請(qǐng)求body
    def get_request_body(self,row):
        col = int(data_config.get_body())
        request_body = self.opera_excel.get_cell_value(row,col)
        if request_body == '':
            return None
        else:
            return request_body

    # 獲取期望結(jié)果
    def get_expect_result(self,row):
        col = int(data_config.get_expect())
        expect_result = self.opera_excel.get_cell_value(row,col)
        if expect_result != '':
            return expect_result
        else:
            return None

    # 獲取用例參數(shù)方式
    def get_case_params_type(self,row):
        col = int(data_config.get_case_params_type())
        case_paramas_type = self.opera_excel.get_cell_value(row,col)
        return case_paramas_type

    # 獲取parameters
    def get_request_params(self,row):
        col = int(data_config.get_parameters())
        request_params = self.opera_excel.get_cell_value(row,col)
        return request_params

    def get_case_id(self,row):
        col = int(data_config.get_id())
        id = self.opera_excel.get_cell_value(row,col)
        return id

operate_excel.py:

#!/usr/bin/python
# -*- coding:utf-8 -*-

import xlrd

class OperateExcel:

    def __init__(self,file_name,sheet_id=0):

        self.file_name = file_name
        self.sheet_id = sheet_id
        self.data = self.get_data_contents()
        self.new_path = '../NewTest/report/'

    # 獲取sheet內(nèi)容
    def get_data_contents(self):
        data = xlrd.open_workbook(self.file_name)
        table = data.sheet_by_index(self.sheet_id)
        return table

    # 獲取單元格行數(shù)
    def get_lines(self):
        tables = self.get_data_contents()
        return tables.nrows

    # 獲取某個(gè)單元格內(nèi)容
    def get_cell_value(self,row,col):
        return self.data.cell_value(row,col)

handle_requests.py:

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
封裝get/post請(qǐng)求
"""

import requests

class SendRequest:

    def request_main(self, method, request_url, params=None, data=None, headers=None):

        try:

            res = requests.request(method, request_url, params=params, data=data, headers=headers, timeout=5)

            return res
        except (requests.ConnectionError, requests.HTTPError, requests.URLRequired, requests.Timeout,
                requests.ConnectTimeout), e:
            print e

run.py

#!/urs/bin/python
# -*- coding:utf-8 -*-

from public.get_data import GetData
from public.handle_requests import SendRequest
import json
import requests
from nose_parameterized import parameterized
import unittest

class TestRunCase():

    def __init__(self, file_name):
        self.run_request = SendRequest()
        self.data = GetData(file_name)
        self.path = file_name

    def get_params(self):

        param_dic = []
        try:

            rows_count = self.data.get_case_linese()  # 獲取用例excel條數(shù)
            for i in range(1, rows_count):
                method = self.data.get_request_method(i)
                type = self.data.get_case_params_type(i)
                query = self.data.get_request_query(i)
                body = self.data.get_request_body(i)
                url = self.data.get_request_url(i)
                params = self.data.get_request_params(i)

                if type == "URL Path":
                    request_url = url + str(params)
                    params = None
                    data = None
                    headers = None
                elif type == "No Type":
                    request_url = url
                    params = None
                    data = None
                    headers = None
                elif type == "Query":
                    request_url = url
                    params = json.loads(query)
                    data = None
                    headers = None
                elif type == "URL Path And Query":
                    request_url = url + params
                    params = json.loads(query)
                    data = None
                    headers = None
                elif type == "Body":
                    request_url = url
                    body = json.loads(body)
                    params = None
                    data = json.dumps(body)
                    headers = None
                else:
                    print "請(qǐng)求方式不在范圍內(nèi)!"

                response = self.run_request.request_main(method, request_url, params=params, data=data,
                                                         headers=headers)
                actual_results = response.status_code
                expect_results = int(self.data.get_expect_result(i))
                case_id = i + 1

                param_dic.append((case_id, expect_results, actual_results))

            return param_dic

        except (requests.ConnectionError, requests.HTTPError, requests.URLRequired, requests.Timeout,
                requests.ConnectTimeout) as e:
            print e

path = "/Users/shifenyuanqi/Desktop/test.xlsx"
params_list = TestRunCase(path).get_params()
print params_list


class TestRun(unittest.TestCase):

    @parameterized.expand(params_list)
    def test_run(self, name, expect_res, actual_res):
        self.assertEqual(expect_res, actual_res)

最后run一波咧栗,自動(dòng)遍歷excel的每條case執(zhí)行請(qǐng)求生成結(jié)果:


run_results.png

當(dāng)然還有寫(xiě)入excel執(zhí)行結(jié)果啦逆甜,生成測(cè)試報(bào)告,自動(dòng)持續(xù)集成到Jenkins這些這里就暫時(shí)不贅述了~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末致板,一起剝皮案震驚了整個(gè)濱河市交煞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斟或,老刑警劉巖素征,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異萝挤,居然都是意外死亡御毅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)怜珍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)端蛆,“玉大人,你說(shuō)我怎么就攤上這事酥泛〗穸梗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵柔袁,是天一觀的道長(zhǎng)呆躲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捶索,這世上最難降的妖魔是什么插掂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上辅甥,老公的妹妹穿的比我還像新娘箩祥。我一直安慰自己,他們只是感情好肆氓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著底瓣,像睡著了一般谢揪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捐凭,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天拨扶,我揣著相機(jī)與錄音,去河邊找鬼茁肠。 笑死患民,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垦梆。 我是一名探鬼主播匹颤,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼托猩!你這毒婦竟也來(lái)了印蓖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤京腥,失蹤者是張志新(化名)和其女友劉穎赦肃,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體公浪,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡他宛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欠气。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厅各。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晃琳,靈堂內(nèi)的尸體忽然破棺而出讯检,到底是詐尸還是另有隱情,我是刑警寧澤卫旱,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布人灼,位于F島的核電站,受9級(jí)特大地震影響顾翼,放射性物質(zhì)發(fā)生泄漏投放。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一适贸、第九天 我趴在偏房一處隱蔽的房頂上張望灸芳。 院中可真熱鬧涝桅,春花似錦、人聲如沸烙样。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谒获。三九已至蛤肌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間批狱,已是汗流浹背裸准。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赔硫,地道東北人炒俱。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爪膊,于是被迫代替她去往敵國(guó)和親权悟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容