关于python:pythonrequestsexcelunittestddt接口自动化数据驱动并生成html报告优化版

3次阅读

共计 6057 个字符,预计需要花费 16 分钟才能阅读完成。

本文章内容是基于上海 - 悠悠的版本,进行了优化,减少了局部内容,具体请查阅下文。
@TOC

1、原文链接

python+requests+excel+unittest+ddt 接口自动化数据驱动并生成 html 报告

2、批改前后框架区别

批改前:

批改后:

3、次要批改内容

  • 减少:token 关联(token 获取和保留)
  • 减少:cookie 关联(cookie 获取和保留)
  • 减少:发送邮件(应用 SMTP)
  • 批改:HTML 报告模板中的款式和 ddt 用例的题目
  • 减少:logo 日志

### 4、具体批改内容阐明
#### 4.1、减少 token 关联
##### 4.1.1、token 获取 get_token.py

import json
import requests
from common.operation_json import OperetionJson

class OperationHeader:

    def __init__(self, response):
        # self.response = json.loads(response)
        self.response = response

    def get_response_token(self):
        '''获取登录返回的 token'''
        token = {"data":{"token":self.response['data']['token']}}
        #token = {"token": self.response['data']['token']}
        return token

    def write_token(self):
        op_json = OperetionJson()
        op_json.write_data(self.get_response_token())

    def get_response_msg(self):
        reponse_msg = {"msg":self.response['msg']}
        #print("reponse_msg:", reponse_msg)
        return reponse_msg
4.1.2、token 保留 operation_json.py
#coding:utf-8
import json
class OperetionJson:

    def __init__(self,file_path=None):
        if file_path  == None:
            self.file_path = '../case/cookie.json'
        else:
            self.file_path = file_path
        self.data = self.read_data()

    #读取 json 文件
    def read_data(self):
        with open(self.file_path, 'r', encoding='utf-8') as fp:
            data1 = fp.read()
            if len(data1) > 0:
                data = json.loads(data1)
            else:
                data = {}
            return data

    #依据关键字获取数据
    def get_data(self,id):
        print(type(self.data))
        return self.data[id]

    #写 json
    def write_data(self,data):
        with open('../case/token.json','w') as fp:
            fp.truncate()  # 先清空之前的数据,再写入,这样每次登录的 token 都是不一样的
            fp.write(json.dumps(data))
4.1.3、token 的读取 base_api.py

在原代码中退出 token 的读取,即把 token 退出到 heasers 中

 # 申请头部 headers
    try:
        headers = eval(testdata["headers"])
        if testdata["token"] == "yes":
            op_json = OperetionJson("../case/token.json")
            token = op_json.get_data('data')
            headers = dict(headers, **token)
        print("申请头部:", headers)
        log.info("申请头部:", headers)
    except:
        headers = None

4.2、减少 cookie 关联

实现逻辑和获取 token 截然不同

4.2.1、cookie 获取 get_token.py

间接在获取 token 的 get_token.py 中退出,而这里的 token 格局须要依据本人的业务批改

    def get_response_cookie(self):
        cookie1 = requests.utils.dict_from_cookiejar(self.response.cookies)
        cookie = {"data":{"gfsessionid":cookie1["gfsessionid"]}}
        # {"data": {"token": self.response['data']['token']}}
        print("cookie:", cookie)
        return cookie
    def write_cookie(self):
        op = OperetionJson()
        op.write_mydata(self.get_response_cookie())
4.2.2、cookie 保留 operation_json.py

间接在 operation_json.py 中退出

    def write_mydata(self,data):
        with open('../case/cookie.json','w') as fp:
            fp.truncate()  # 先清空之前的数据,再写入,这样每次登录的 token 都是不一样的
            fp.write(json.dumps(data))
4.2.3、cookie 的读取 base_api.py

间接在 base_api.py 中退出

    try:
        headers = eval(testdata["headers"])
        if testdata["cookie"] == "yes":
            op_json = OperetionJson("../case/cookie.json")
            token1 = op_json.get_data('data')
            headers = dict(headers, **token1)
        print("申请头部:", headers)
        log.info("申请头部:", headers)
    except:
        headers = None

4.3、减少邮件服务

4.3.1、邮件服务封装 send_mail.py
#coding=utf-8
from email.mime.text import MIMEText
import time
import smtplib
import getpass
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import email
import os

def sendmain(file_path,mail_to = 'xxxxx@126.com'):
    mail_from = 'yyyyy@126.com'
    f = open(file_path,'rb')
    mail_body=f.read()
    f.close()
    
    #msg = email.MIMEMultipart.MIMEMultipart()
    msg = MIMEMultipart()

    # 结构 MIMEBase 对象做为文件附件内容并附加到根容器  
    contype = 'application/octet-stream'  
    maintype, subtype = contype.split('/', 1)  
    ## 读入文件内容并格式化  
    data = open(file_path, 'rb')
    #file_msg = email.MIMEBase.MIMEBase(maintype, subtype)
    file_msg = MIMEBase(maintype, subtype)
    file_msg.set_payload(data.read())  
    data.close( )  
    #email.Encoders.encode_base64(file_msg)
    encoders.encode_base64(file_msg)
    ## 设置附件头  
    basename = os.path.basename(file_path)  
    file_msg.add_header('Content-Disposition',  
                        'attachment', filename = basename)  
    msg.attach(file_msg)  
    print(u'msg 附件增加胜利')
    
    msg1 = MIMEText(mail_body,_subtype='html',_charset='utf-8')
    msg.attach(msg1)
    
    if isinstance(mail_to,str):
        msg['To'] = mail_to
    else: 
        msg['To'] = ','.join(mail_to)
    msg['From'] = mail_from
    msg['Subject'] = u'xxxxxxxxx 接口自动化测试' # 邮件题目
    msg['date']=time.strftime('%Y-%m-%d-%H_%M_%S')
    print(msg['date'])

    smtp = smtplib.SMTP()
    smtp.connect('smtp.126.com')
    smtp.login('yyyyyy@126.com','aaaaaaaaaa') # 这里的明码是邮件第三方客户端认证明码
    smtp.sendmail(mail_from, mail_to, msg.as_string())
    smtp.quit()
    print('email has send out !')
'''if __name__=='__main__':
    sendmain('../report/2017-08-18-10_18_57_result.html')
'''
4.3.2、邮件调用 run_this.py

间接在主函数入口中调用

sendmain(htmlreport, mail_to=['hhhhhhhh@126.com', 'jjjjjj@126.com', 'uuuuuu@126.com']) #多个收件人的话,间接在列表中,用, 号隔开即可 

4.4、批改 html 报告模板

4.4.1、批改报告中用例的题目,批改 ddt 源码

①原报告用例的题目:
因为应用 ddt,所以 ddt 格局中用例题目是 test_api_数字结尾的用例名称,如果要自定义须要批改 ddt 源码

②批改后的报告题目:

③ 如何批改?
能够参考之前的博文:
unittest 中应用 ddt 后生成的测试报告名称如何批改?(如 test_api_0 批改成 test_api_0_titile)

def mk_test_name(name, value, index=0):
    """
    Generate a new name for a test case.

    It will take the original test name and append an ordinal index and a
    string representation of the value, and convert the result into a valid
    python identifier by replacing extraneous characters with ``_``.

    We avoid doing str(value) if dealing with non-trivial values.
    The problem is possible different names with different runs, e.g.
    different order of dictionary keys (see PYTHONHASHSEED) or dealing
    with mock objects.
    Trivial scalar values are passed as is.

    A "trivial" value is a plain scalar, or a tuple or list consisting
    only of trivial values.
    """

    # Add zeros before index to keep order

    index = "{0:0{1}}".format(index + 1, index_len,)
    if not is_trivial(value) and type(value) is not dict: # 减少的中央,减少 value 的字典判断

        return "{0}_{1}_{2}".format(name, index, value.name) # 批改的中央,减少返回的值
    if type(value) is dict: # 减少的中央
        try: # 减少的中央
            value = value["name"] + "_" + value["function"] # 减少的中央,name 和 function 必须是 execl 用例中整正存在的表头,这里我是把两个表头合并了(name 是我表格中接口的名称,function 是表格中接口的性能形容)except: # 减少的中央
            return "{0}_{1}".format(name.index) # 减少的中央
    try:
        value = str(value)
    except UnicodeEncodeError:
        # fallback for python2
        value = value.encode('ascii', 'backslashreplace')
    test_name = "{0}_{1}_{2}".format(name, index, value)  # 批改的中央
    return re.sub(r'\W|^(?=\d)', '_', test_name)
4.4.2、减少用例执行人

在 HTMLTestRunner.py 中退出如下,即获取以后用例执行的负载机的用户名

 DEFAULT_TESTER = getpass.getuser()

4.5、减少 log 日志

4.5.1、在框架入口中间接退出 run_this.py
# LOG 日志记录
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename=log_path + '/' + now + r"result.log",
                        filemode='w')
    logger = logging.getLogger()
    logger.info(all_case)

具体能够参考之前的博文:
Unittest 接口测试生成报告和日志办法

4.5.2、在其它模块中间接应用即可
log = logging.getLogger()
log.info("申请头部:", headers)

5、其它截图

log 截图:

测试报告:

邮件:

正文完
 0