基于python+requests+unittest实现的接口自动化测试方案

第一篇简书文章献给你,笔芯~

背景简介

最近在做接口测试,每次新功能提交测试,有大量的接口,有些接口参数有几十个,转化成用例,除了每个参数基础的边界值、等价类校验外,还涉及很多业务逻辑的比对测试。由于团队提供的API接口在swagger上面,每次测试在swagger上拼参数测试确实很方便,但是没有个记录,测试完了除非每个团队成员都对接口参数有保存,后续再要验证时要重新拼参数或是找同事拿已经拼好的参数,增加时间成本,而且要回归验证有需要再重新调一次,考虑到这些,做了一些需求调研,决定使用python+requests+unittest实现读取Excel用例自动执行请求。项目落成,在此做个记录,当然仍有许多不足之处需改进。

需求分析

要做一个东西,当然是要先搞清楚需求,即我们的现状,要实现怎样的效果,搞清楚了要实现什么,再来一步一步的任务拆分,然后想办法实现。

首先,开发提供给我们测试的接口是通过swagger自动生成,没有特定的接口文档,swagger上有每个接口详细的参数描述等信息。接口请求类型主要有GET、POST、PUT、DELETE,针对每个类型的接口,传参类型有所不同,不能用通用的requests传参来做拼接请求,如下:


params_type.png

如果类型为URL Path的,做请求时就需要拿到base_url+URL Path,拼接好后作为请求的url;
如果类型为Query的,就需要把参数以key-value的形式进行传参;等等...
依此,设计Excel模板如下:


Excel模板.png

每个团队成员只需要在Excel里面写一次用例,执行的时候记录下每个传参、请求的基础url,预期结果,之后就可以自动回归啦~

接下来就是要怎么实现这个过程了...

实现

就像把大象塞冰箱里面需要几个步骤一样,打开冰箱、把大象塞进去、关上冰箱....(⊙﹏⊙)b....大象根本就塞不下一般的冰箱好不啦~

言归正传,怎样实现这个过程:

1.遍历excel,读取数据
2.处理读取到的数据,拿到我们想要的数据
3.拼接请求,发送request请求
4.拿到response状态值,对比excel预期结果是否一致

架构如图(也说不上什么架构哈~)


项目结构.png

说明:
·operate_excel.py:封装一些操作excel数据表的方法
·get_data.py:封装获取excel值的方法
·data_config.py:配置excel的固定列,封装获取对应列的方法
·handle_requests.py:封装request请求
·run.py:主函数,根据拿到的请求类型判断,拼装请求url及参数自动发送请求,返回实际请求的status_code以及excel的expect。然后作为参数传递给unittest的test方法自动断言

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 -*-
"""
获取单元格中的内容
"""

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行数,即case个数
    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

    # 获取请求方式
    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

    # 获取请求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

    # 获取请求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

    # 获取请求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

    # 获取期望结果
    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

    # 获取用例参数方式
    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内容
    def get_data_contents(self):
        data = xlrd.open_workbook(self.file_name)
        table = data.sheet_by_index(self.sheet_id)
        return table

    # 获取单元格行数
    def get_lines(self):
        tables = self.get_data_contents()
        return tables.nrows

    # 获取某个单元格内容
    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请求
"""

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条数
            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 "请求方式不在范围内!"

                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一波,自动遍历excel的每条case执行请求生成结果:


run_results.png

当然还有写入excel执行结果啦,生成测试报告,自动持续集成到Jenkins这些这里就暂时不赘述了~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容