python unittest excel_python3+excel+unittest+ddt+BeautifulReport实现接口自动化测试

本问主要介绍用excel表格来管理接口用例,采用python+unittest测试框架,结合ddt数据驱动,最后结合BeautifulReport报告插件,生成最终的测试报告

首先,来3张图,了解输入数据,输出结果

1、需要测试的接口case:execel表格管理

2、请求的body:request_data.py文件中字典req_data,用来存放所有case请求的body

3、利用unittest+ddt+BeautifulReport生成HTML测试报告:

其次,附上整个项目的结构图

最后,分解项目运行的细节内容

1、项目的主运行文件:run_ddt.py

(1)、导入我们run_ddt.py文件运行所需要的第三方包# coding=utf-8

import unittest

import time

import os

from BeautifulReport import BeautifulReport

(2)、生成我们需要的report路径curpath = os.path.dirname(os.path.realpath(__file__))

reprot_path = os.path.join(curpath, "report")

(3)、匹配该项目里,以test开头的文件,并添加成一个unittest测试集def add_case(casepath=curpath, rule="test_*.py"):

discover = unittest.defaultTestLoader.discover(casepath, pattern=rule)

return discover

(4)、得到了测试集,便可以运行整个测试集里面的测试用例def run_case(all_case, reportpath=reprot_path):

now = time.strftime("%Y%m%d%H%M%S")

print('测试报告生成地址: %s' % reportpath)

BeautifulReport(all_case).report(description='用例执行情况', filename='测试报告_' + str(now), report_dir=reportpath)# 该文件的main函数入口:

if __name__ == '__main__':

cases = add_case()

run_case(cases)

写到上面第3步的时候,你就会联想到,我们后续肯定会编写一个test_开头的py文件,而里面就是我们的测试内容。

是的,我们第2个文件,就是编写我们的测试代码,也可以说是我们的测试思路或者是测试的步骤。

2、测试思路:test_case_all.py

(1)、导入该文件所运行的包,以及从公用模块导入公用函数from myrequests import MyRequests

from common.operationExcel import OperationExcel

from common.operationReqData import OperationReqDate

from common.dependentData import DependentData

from ddt import ddt, data, unpack

from common.operationSQL import connectSQL

from config import *

import json

import unittest

(2)、我们是从一个excel的sheet表拿的数据,要想把这些数据利用ddt来驱动,就需要把整个excel表的数据全部拿出来,然后再利用ddt来分割数据,在用切割的数据进行单个case测试class GetReqData(object):

# 初始化excel操作模块类,才能调用该类下的函数方法

def __init__(self):

self.operation_excel = OperationExcel()

#  利用excel类里面的方法,获取excel表格的所有数据

def get_data(self):

exe_data_all = []    # 定义一个空的列表,存放excel表格的数据

rows_count = self.operation_excel.get_all_lines()    # 获取表格中有多少行数据

for i in range(1, rows_count):    # 循环遍历取excel表格的数据,去除第一行

exe_data = self.operation_excel.get_a_row_data(i)    # 把每一行的数据都取出

exe_data_all.append(tuple(exe_data))    # 取出的数据,都添加到定义空列表中

return exe_data_all    # 取值完成后,把所有的数据返回出去# 单独的把获取数据函数进行调用一次,这样ddt数据驱动,才有数据作为参数传入

get_req_data = GetReqData()

req_data = get_req_data.get_data()

(3)、ddt来驱动excel表的数据,获取到的excel数据是一个list类型,提取每一行的数据就显示轻松多了。提取完数据,就可以进行request请求测试了。# 采用ddt数据驱动,在运行的类前,就需要先运行ddt的装饰器函数,故需要在Run类前加上@ddt

@ddt

class Run(unittest.TestCase):

#  集成unittest.TestCase方法,然而需要初始化,在unittest里__init__函数无法使用,所以我们就用到unittest里的setUp、 tearDown这样函数来做类函数的初始化,这里初始化只需要运行一次,这里我采用了setUpClass这个函数来实现

@classmethod

def setUpClass(cls):

print('------执行开始------')

cls.operation_excel = OperationExcel()

cls.operation_req_data = OperationReqDate()

cls.dependent_data = DependentData()

cls.m = MyRequests()

cls.host = HOST    # 从config文件获取host,这样切换地址不用改excel表的url内容

cls.new_data_dict = {}

@classmethod

def tearDownClass(cls):

print('------执行完毕------')

#     初始化工作已完成,那就进入我们重点、重点、重点了

@data(*req_data)    # ddt下data可以把数据进行切分返回数据,具体可参照ddt使用

@unpack    # ddt下的一个方法,目的是把每一行数据分开传参,具体使用ddt详解

def test_case(cls,  *exe_data):

# 这里是判断需要执行SQL语句

if exe_data[3] == "SQL":

sql = exe_data[5]

connectSQL(exe_data[7], sql, cls.new_data_dict)

'''

这里判断case是都需要执行(运行的流程重点就在此)

我们从每一行数据取出来是一个list,根据list的下标,获取excel表格的值;

1、取决于该case是否运行,如果运行,就往下取值,反之,则不用管;

2、获取该case请求的body值,根据excel的req_data字段,取对应的值

3、如果该case有依赖,就需要走依赖函数,进行键位值的替换,实现实时数据变动;

4、进行接口的请求(如果没有依赖,则可以跳过第3步)

5、进行预期结果与实际结果的对比

6、最后,如果该case需要提取某个字段的值,根据键位,在返回的内容中进行提取

注意:new_data_dict这个字典,是存放替换的值,格式是key=value,key是我们自定义的名称,value则是从返回值提取的值,提取数据必须在替换数据之前就有值,不然会报错,因为提取的数据没有值,替换的时候就无法找到值进行替换。提取值是用了jsonpath的方法提取,替换则是采用了自己定义的,以"."的方式代替层级关系。

'''

elif exe_data[3] == 'YES':       # 第1步,判断是否运行

req_data = cls.operation_req_data.get_req_data(exe_data[5])    # 第2步取body

print('执行的用例ID: ', exe_data[0])

data = json.loads(exe_data[9])    # 数据转换,怕数据格式错误。

if exe_data[7] != '':    # 判断请求的body是否有依赖,此处判断值为有依赖

req_data = cls.dependent_data.replace_req_data(exe_data[7], req_data, cls.new_data_dict)    # 第3步,有依赖,从提取值获取进行替换(注:提取值必须有值)

res = cls.m.myrequests(cls.host + exe_data[2], req_data, exe_data[4], exe_data[6])        # 第4步,进行数据请求

for key, value in data.items():        # 第5步,预期结果与实际结果的对比

res_value = cls.dependent_data.replace_data(key, res)

cls.assertEqual(value, res_value)

if exe_data[8] != '':        # 判断是否需要提取

cls.new_data_dict = cls.dependent_data.dependent_data(exe_data[8], res, cls.new_data_dict)        # 第6步,根据键位,在返回的内容提取值

else:

res = cls.m.myrequests(cls.host + exe_data[2], req_data, exe_data[4], exe_data[6])        # 第4步,进行无body替换的接口请求

for key, value in data.items():    # 第5步,预期结果与实际结果对比

res_value = cls.dependent_data.replace_data(key, res)

cls.assertEqual(value, res_value)

if exe_data[8] != '':    #  判断是否需要提取

cls.new_data_dict = cls.dependent_data.dependent_data(exe_data[8], res, cls.new_data_dict)    # 第6步,根据提取的键位,在返回值中提取对应的值# 该文件的程序入口

if __name__ == '__main__':

unittest.main()

3、整个项目的脊柱已经弄好,现在就需要各个内容来支配整个项目

----从test文件整理出,我们可以察觉到缺少的函数文件,我们一一列出:

---1。excel表格的数据获取方法

---2。请求body的数据获取方法

---3。提取值的方法

---4。替换body的方法

---5。接口请求的方法

从这5点中,我们就来一一编写需要的方法:

3-1、excel的获取数据方法:operationExcel.py

在test文件里,我们发现了这两句代码,属于excel的操作rows_count = self.operation_excel.get_all_lines()

exe_data = self.operation_excel.get_a_row_data(i)

那么我们就需要在common公用文件下新建一个operationExcel.py文件,来针对excel表格数据的操作# coding=utf-8

import xlrd, os, time, xlwt

from xlutils.copy import copy

class OperationExcel(object):

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 = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data/ApiList1.xlsx')

self.data = self.get_data()

# 获取数据

def get_data(self):

data = xlrd.open_workbook(self.file_name)

tables = data.sheets()[self.sheet_id]

return tables

# 获取sheet下的行数

def get_all_lines(self):

tables = self.data

return tables.nrows

# 获取某一行的内容

def get_a_row_data(self, row_num):

tables = self.data

row_data = tables.row_values(row_num)

return row_data

3-2、body的获取数据方法:operationReqData.py

在test文件里,我们会发现以下的代码:req_data = cls.operation_req_data.get_req_data(exe_data[5])

这样的代码,是我们从excel表取标识字段,到request_data文件里req_data取对应key的value,这样body就能取出来了from data import request_data

class OperationReqDate(object):

def __init__(self):

self.data = request_data.req_data  # 修改req_data的文件名

# 根据关键key来获取req_data文件的内容

def get_req_data(self, key):

if key == '':

return None

return self.data.get(key)

3-3、根据excel的数据,提取返回值的内容:dependentData.py

我们在现实的测试中,往往发现,这个接口运行的时候,会调用上一个接口的数据,而且还有一些数据值,都是重复调用,总不可能请求一个接口,去多次调用其他接口吧,这样就导致了接口的请求量变大了,增加了服务器的负载能力。

解决方案:我们在请求前,我们新建一个空的字典,自定义key来获取对应的value值,成成一个新的字典,请求body需要的时候,就直接从这里取值,这样就减少了请求次数。

然而,在test文件中,我们会发现有这样的代码存在:if exe_data[8] != '':

cls.new_data_dict = cls.dependent_data.dependent_data(exe_data[8], res, cls.new_data_dict)

这样是进行判断是否有提取值,有则需要提取,反之则不需要,然后我们的提取方法:

提取方法的思路:

1、根据依赖的键位,去遍历返回的res,键位提取的格式:id=(result.id)

2、找到了键位的值后,把键位的key作为字典的key存放,找的值当做value存放,组成一个新的字典# 根据exe表格中的key=(value)来获取一个新增的dict-data

def dependent_data(self, dependent_data, res, data_dict):

exe_data = dependent_data.split('\n')

# print('11: ', exe_data)

for data in exe_data:

data_value = data.split('=')

# print('data_value:', data_value)

dependent_key = data_value[1]

value = self.replace_data(dependent_key, res)

data_dict[data_value[0]] = value

return data_dict

在此时,就会发现一个新的语句:value = self.replace_data(dependent_key, res)

然而我们就需要在该文件下再创建一个函数方法,这里提取的方法是采用jsonpath:def replace_data(self, data_key, data_value):

"""

:param data_key: 依赖的key值

:param data_value: 遍历的返回页面数据

:return:

"""

try:

json_exe = parse(data_key)

madles = json_exe.find(data_value)

except Exception as msg:

print(msg)

return [madle.value for madle in madles][0]

这样我们的程序就不会报错,该方法的用途我也不做多解释,网上有类似的专业讲解。那么我们继续我们项目其他方法解析

3-4、替换请求的body里的键位值:dependentData.py

在3-3中,我已经讲解了提取值的方法,主要是为了便于替换的时候需要,在新的一个字典里,我们只有传入key,就能把之前接口请求返回的value取到,进行替换,就可以直接请求了,我在excel表的替换值的格式:id={{user_id}},格式可以根据自己喜欢来写,切割点就需要重新变化下即可。# 根据exe的表格key={{value}}去替换值

def replace_req_data(self, dependent_data, req_data, new_data_dict):

exe_data = dependent_data.split('\n')

for data in exe_data:

data_value = data.split('=')

value = new_data_dict.get(data_value[1][2:-2])

req_data = self.check_json_data.check_json_data(req_data, data_value[0], value)

return req_data

在上面的方法中,发现有一行新的代码req_data = self.check_json_data.check_json_data(req_data, data_value[0], value)

这行代码是进行替换的操作,遍历操作替换的工作量大,因此我们重新编写一个文件来实现此功能:

3-4-1、数据替换方法:checkJsonData.py

遍历我们的请求的body,根据对应的键位,去实现value的一个更新,实现数据更新功能# coding=utf-8

from httprunner import exceptions, logger

from httprunner.compat import OrderedDict, basestring, is_py2

class Check_Json_Data(object):

# 替换json数据中对应的value

def change_json(self, json_content, query, new, delimiter='.'):

raise_flag = False

response_body = u"response body: {}\n".format(json_content)

try:

keys = query.split(delimiter)

if len(keys) == 1:

if isinstance(json_content, (list, basestring)):

json_content[int(keys[0])] = new

elif isinstance(json_content, dict):

json_content[keys[0]] = new

if len(keys) > 1:

for key in keys:

if isinstance(json_content, (list, basestring)):

return self.change_json(json_content[int(key)], ".".join(keys[1:]), new, delimiter='.')

elif isinstance(json_content, dict):

return self.change_json(json_content[key], ".".join(keys[1:]), new, delimiter='.')

except (KeyError, ValueError, IndexError):

raise_flag = True

if raise_flag:

err_msg = u"Failed to extract! => {}\n".format(query)

err_msg += response_body

logger.log_error(err_msg)

raise exceptions.ExtractFailure(err_msg)

# 数据替换的方法

def check_json_data(self, old_req_data, dependent_key, values):

"""

把旧的请求数据,根据键位,替换掉旧数据

:param old_req_data: json文件的旧数据

:param dependent_key: excel表中的键位值

:param values: 获取依赖的接口返回的键位值,也就是新值

:return: 返回一个替换后的请求数据

"""

self.change_json(old_req_data, dependent_key, values)

return old_req_data

3-5、请求的方法:myrequests.py

我们采用request模块进行url的请求,这里需要更新自己的token,各个平台不同,token的取值也不同,这个因系统而异。

首先,我们需要提取token# 获取token

def login(self):

global token

if "Authorization" in self.s.headers.keys():        # 判断是否存在token,如果有就直接跳过

# print('--------token is exits!!---------')

return self.s

else:

excel_data = self.operation_excel.get_a_row_data(1)

url = self.host + excel_data[2]

req_data = login_data

res = self.s.post(url, json=req_data)

r = res.content.decode('utf-8')

r = json.loads(r)

token = r['result']['token']

self.s.headers.update({"Authorization": token})

print("----------token create successfully!--------")

return self.s

其次,封装自己的请求方式。网上有很多种封装方式,小伙伴可以选择自己喜欢的封装方式,这里我贴上我自己的封装方式,方法不完美,能实现就好。# 自定义请求函数

def myrequests(self, url, req_data, req_type, data_type):

"""

自定义请求函数

:param url: 请求的url

:param req_data: 请求的data

:param req_type: 请求方式

:param data_type: 数据的传递格式

:return: res页面结果

"""

if req_type == "POST":  # 判断请求方式:POST

if data_type == 'JSON':  # 判断请求参数的数据类型

# post_data = json.loads(req_data)

# res = self.login().post(url, json=req_data)

res = self.login().post(url, json=req_data)

elif data_type == '':

res = self.login().post(url, data=req_data)

else:

res = self.login().post(url, data=req_data)

elif req_type == "GET":

if data_type == 'JSON':

get_params = json.loads(req_data)

res = self.login().get(url, params=get_params)

elif data_type == '':

res = self.login().get(url, params=req_data)

else:

res = self.login().get(url, params=req_data)

elif req_type == "DELETE":

if data_type == 'JSON':

get_params = json.loads(req_data)

res = self.login().delete(url, params=get_params)

elif data_type == '':

res = self.login().delete(url, params=req_data)

else:

res = self.login().delete(url, params=req_data)

print("request_req_url: ", res.url)

print("request_req_data: ", req_data)

res = res.content

res = json.loads(res.decode('utf-8'))

print('res: ', res)

return res

在附上配置文件内容:config# coding=utf-8

HOST = "http://172.16.62.66"

# HOST = "http://172.16.62.71"

SQL_IP = "172.16.62.66"

# SQL_IP = "172.16.62.71"

db_message = {

"host": SQL_IP,

"username": "root",

"password": "123456",

"port": 3306,

"charset": "utf8"

}

login_url = HOST + '/xxx-x'x'x'x/login/login'

login_data = {

"mobile": "18100000000",

"smsCode": "888888"

}

总结:项目的方法封装不是很好,这里介绍我使用的办法,如果有更好的方法,方便留言,多多研究,让自动化测试更加完美。邮件的发送方法,请求头文件的更新,我这边都没做,后期实现了,再更新。。

你可能感兴趣的:(python,unittest,excel)