Python+Excel接口测试

Python接口测试需求

自动读取Excel表格中的接口测试用例,执行从Excel中读取的用例,接口测试完成后,发送邮件通知。
整个测试过程实现步骤:
1、编写Excel测试用例模板
2、读取Excel表格中的测试用例
3、组装URL、请求参数
4、发送请求
5、整理测试结果并发送邮件

1、编写Excel测试用例模板

Excel编写的测试用例没有固定的模板,用例的格式根据实际测试需求或者接口的字段来确定所需要的格式
测试用例模板

读取Excel中的用例

使用Python对Excel操作时,需要安装xlrd

def test_case_in_excel(test_case_file):
    test_case_file = os.path.join(os.getcwd(), test_case_file)      # 获取测试用例的路径
    if not os.path.exists(test_case_file):
        Logger().info('测试用例excel文件不存在或路径有误!')
        sys.exit()      # 找不到指定测试文件,就退出程序 os.system("exit")是用来退出cmd的
    test_case = xlrd.open_workbook(test_case_file)      # 读取excel文件
    table = test_case.sheets()[0]       # 获取第一个sheet,下表从0开始
    error_case = []     # 记录错误用例
    '''读取表格中的用例,其实就像一个二维数组'''
    for i in range(1, table.nrows):
        api_id = str(int(table.cell_value(i, 0))).replace("\n", "").replace("\r", "")
        api_name = table.cell_value(i, 1).replace("\n", "").replace("\r", "")
        api_host = table.cell_value(i, 2).replace("\n", "").replace("\r", "")
        api_url = table.cell_value(i, 3).replace("\n", "").replace("\r", "")
        api_method = table.cell_value(i, 4).replace("\n", "").replace("\r", "")
        api_data_type = table.cell_value(i, 5).replace("\n", "").replace("\r", "")
        api_request_data = table.cell_value(i, 6).replace("\n", "").replace("\r", "")
        api_check_point = table.cell_value(i, 7).replace("\n", "").replace("\r", "")
        try:
            # 调用接口请求方法
            status, res = interface_test(api_id, api_name, api_host, api_url, api_method, api_data_type, api_request_data, api_check_point)
            if status != 200:
                # append()只接受一个参数,所以四个参数要用一个括号括起来
                # 请求失败,则向error_case中增加一条记录
                error_case.append((api_id + " " + api_name, str(status), api_host + api_url))
        except Exception as e:
            Logger().error(e)
            Logger().info("第{}个接口请求失败,请检查接口是否异常.".format(api_id))
            # 访问异常,则向error_case中增加一条记录
            error_case.append((api_id + " " + api_name, "请求失败", api_host + api_url))
    return error_case

组装URL、参数并发送请求

根据Method字段来判断,是使用POST还是GET方式请求
根据Request_Data_Type字段来判断参数传递方式,选择不同的参数处理方式
根据Request_Data字段来判断是否带参数,选择不同的请求处理方式
def interface_test(api_id, api_name, api_host, api_url, api_method, api_data_type, api_request_data, api_check_point):
    # 构造请求头headers
    headers = {
        "Content-Type": "application/json"
    }
    # 判断请求方式,若是GET,调用get请求;若是POST,调用post请求
    if api_method == "GET":
        # 判断get请求是否带参数,然后选择不同get请求方式
        if bool(api_request_data) == False:
            res = requests.get(url=api_host + api_url)
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
        else:
            res = requests.get(url=api_host + api_url, params=api_request_data)
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
    elif api_method == "POST":
        # 判断api_data_type的类型,选择不同post请求
        if api_data_type == 'Json':
            res = requests.post(url=api_host+api_url, data=api_request_data, headers=headers)
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
        else:
            res = requests.post(api_host+api_url, data=json.loads(api_request_data))
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
    return status_code, "请求方式错误"

整理测试结果、发送邮件通知

通过QQ邮箱发送测试结果
def send_email(text):
    today = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
    sender, recevier, smtpserver, username, password = get_conf()
    subject = "[api_test]接口自动化测试结果通知{}".format(today)
    msg = MIMEText(text, 'html', 'utf-8')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = recevier
    try:
        smtp = smtplib.SMTP_SSL(smtpserver, 465)
        smtp.login(sender, password)
        smtp.sendmail(sender, recevier, msg.as_string())
        Logger().info("发送成功")
    except Exception as e:
        Logger().info("发送失败,因为:{}.".format(e))
    # finally:
    #     smtp.quit()
用yaml文件来编写config文件
email:     # 缩进时吗,不允许使用Tab,只允许使用空格
  sender: 54******[email protected]
  recevier: 54******[email protected]
  smtpserver: smtp.qq.com
  username: *****
  password: *************

读取config配置文件

# 读取配置文件
def get_conf():
    config_path = os.path.join(os.getcwd(), 'config.yml')
    f = open(config_path, "r", encoding='utf-8')
    cfg = f.read()
    dt = yaml.load(cfg)
    sender = dt['email']['sender']
    recevier = dt['email']['recevier']
    smtpserver = dt['email']['smtpserver']
    username = dt['email']['username']
    password = dt['email']['password']
    return sender, recevier, smtpserver, username, password

封装logging

在使用logging的过程中,会出现日志,重复打印

原因在于:logger封装好后,调用的时候,会根据getLogger(name)里的name获取同一个logger,但是这个logger里已经有了之前添加的handler了,再次调用时又会添加一个handler。所以,这个logger里面就会又多了一个handler,依次类推,调用几次就会有几个handler【后面有重复打印的图片说明】

针对重复打印的问题,提出4个解决方法:(感谢强大的网民 -

① 每次创建不同name的logger,这样每次都是新的logger,就不会添加多个handler了(ps:该方法不推荐)
② 每次用完logger后,使用removeHandler()把logger里面的handler移除掉
③ 在使用logger之前,进行判断,如果已经有handler了,就不在添加了 (ps:推荐使用此种方法)
④ 每次使用完logger后,使用pop()把logger列表的handler移除

# -*- coding:utf-8 -*-
import logging
import logging.handlers
import time


class Logger(object):
    def __init__(self):
        self.logger = logging.getLogger()   # 创建一个logger
        self.logger.setLevel(logging.INFO)  # logger等级总开关

        # 定义log的输出格式
        self.formatter = logging.Formatter(
            '[ %(asctime)s ]--%(threadName)-10s %(thread)d  line:%(lineno)d  [%(levelname)s] *** '
            '%(message)s',
            '%Y-%m-%d %H:%M:%S')

        '''创建一个handler输出到文件'''
        t = time.localtime()    # 获取本地时间
        now = time.strftime("%Y%m%d%H%M%S", t)  # 转化时间格式
        # self.logfile = logging.FileHandler('test_'+now+'.log', encoding='UTF-8')    # 以转换的时间来命名log文件
        # self.logfile.setFormatter(self.formatter)
        # self.logfile.setLevel(logging.INFO)
        # self.logger.addHandler(self.logfile)
        '''
        为了避免log文件中出现重复打印,在handler前面加一个判断,如果已经有handler了,则不再添加handler
        '''
        if not self.logger.handlers:
            self.logfile = logging.FileHandler('test_'+now+'.log', encoding='UTF-8')
            self.logfile.setFormatter(self.formatter)
            self.logfile.setLevel(logging.INFO)
            self.logger.addHandler(self.logfile)

        '''创建一个handler,用于输出到控制台'''
        self.consle = logging.StreamHandler()
        self.consle.setFormatter(self.formatter)
        self.consle.setLevel(logging.INFO)

        self.logger.addHandler(self.consle)

    '''
    1、使用removeHandler()方法可以解决重复打印的问题
    2、会出现重复打印的原因在于:logger封装好后,调用的时候,会根据getLogger(name)里的name获取同一个logger,但是这个logger里已经有了之前添加的handler了,
       再次调用时又会添加一个handler。所以,这个logger里面就会又多了一个handler,依次类推,调用几次就会有几个handler
    3、针对重复打印的问题,提出4个解决方法:(感谢强大的网民^-^)
    ① 每次创建不同name的logger,这样每次都是新的logger,就不会添加多个handler了(ps:该方法不推荐)
    ② 每次用完logger后,使用removeHandler()把logger里面的handler移除掉
    ③ 在使用logger之前,进行判断,如果已经有handler了,就不在添加了 (ps:推荐使用此种方法)
    ④ 每次使用完logger后,使用pop()把logger列表的handler移除
    '''
    # 日志的5个级别对应的5个函数
    def debug(self, msg):
        self.logger.debug(msg)
        self.logger.removeHandler(self.consle)

    def info(self, msg):
        self.logger.info(msg)
        self.logger.removeHandler(self.consle)

    def warn(self, msg):
        self.logger.warning(msg)
        self.logger.removeHandler(self.consle)

    def error(self, msg):
        self.logger.error(msg)
        self.logger.removeHandler(self.consle)

    def critical(self, msg):
        self.logger.critical(msg)
        self.logger.removeHandler(self.consle)

'''测试封装的logging'''
if __name__ == '__main__':
    log = Logger()
    log.info(12452)
    log.error('d4e4f52d12')
    # Logger('1').info(45454)
    # Logger('2').error(122)

控制台重复打印

Python+Excel接口测试_第1张图片

保存的日志文件重复打印

Python+Excel接口测试_第2张图片

下面贴出完成代码

主要代码
# -*- coding:utf-8 -*-
"""
需求:自动读取、执行excel里面的接口测试用例,测试完成后,没返回错误结果并发送邮件通知
一步一步捋清楚需求:
1、设计excel表格
2、读取excel表格
3、拼接url,发送请求
4、汇总错误结果,发送邮件
5、其他注解:
①\n是换行,英文是New line,表示使光标到行首;\r是回车,英文是Carriage return,表示使光标下移一格
"""
import xlrd
import os
import requests
import json
import yaml
import smtplib
import time
import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header
from LYKION.API_Pythony_Excel.log import Logger


def test_case_in_excel(test_case_file):
    test_case_file = os.path.join(os.getcwd(), test_case_file)      # 获取测试用例的路径
    if not os.path.exists(test_case_file):
        Logger().info('测试用例excel文件不存在或路径有误!')
        sys.exit()      # 找不到指定测试文件,就退出程序 os.system("exit")是用来退出cmd的
    test_case = xlrd.open_workbook(test_case_file)      # 读取excel文件
    table = test_case.sheets()[0]       # 获取第一个sheet,下表从0开始
    error_case = []     # 记录错误用例
    '''读取表格中的用例,其实就像一个二维数组'''
    for i in range(1, table.nrows):
        api_id = str(int(table.cell_value(i, 0))).replace("\n", "").replace("\r", "")
        api_name = table.cell_value(i, 1).replace("\n", "").replace("\r", "")
        api_host = table.cell_value(i, 2).replace("\n", "").replace("\r", "")
        api_url = table.cell_value(i, 3).replace("\n", "").replace("\r", "")
        api_method = table.cell_value(i, 4).replace("\n", "").replace("\r", "")
        api_data_type = table.cell_value(i, 5).replace("\n", "").replace("\r", "")
        api_request_data = table.cell_value(i, 6).replace("\n", "").replace("\r", "")
        api_check_point = table.cell_value(i, 7).replace("\n", "").replace("\r", "")
        try:
            # 调用接口请求方法
            status, res = interface_test(api_id, api_name, api_host, api_url, api_method, api_data_type, api_request_data, api_check_point)
            if status != 200:
                # append()只接受一个参数,所以四个参数要用一个括号括起来
                # 请求失败,则向error_case中增加一条记录
                error_case.append((api_id + " " + api_name, str(status), api_host + api_url))
        except Exception as e:
            Logger().error(e)
            Logger().info("第{}个接口请求失败,请检查接口是否异常.".format(api_id))
            # 访问异常,则向error_case中增加一条记录
            error_case.append((api_id + " " + api_name, "请求失败", api_host + api_url))
    return error_case


def interface_test(api_id, api_name, api_host, api_url, api_method, api_data_type, api_request_data, api_check_point):
    # 构造请求头headers
    headers = {
        "Content-Type": "application/json"
    }
    # 判断请求方式,若是GET,调用get请求;若是POST,调用post请求
    if api_method == "GET":
        # 判断get请求是否带参数,然后选择不同get请求方式
        if bool(api_request_data) == False:
            res = requests.get(url=api_host + api_url)
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
        else:
            res = requests.get(url=api_host + api_url, params=api_request_data)
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
    elif api_method == "POST":
        # 判断api_data_type的类型,选择不同post请求
        if api_data_type == 'Json':
            res = requests.post(url=api_host+api_url, data=api_request_data, headers=headers)
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
        else:
            res = requests.post(api_host+api_url, data=json.loads(api_request_data))
            status_code = res.status_code
            if status_code == 200:
                Logger().info("第{}条用例-{}-执行成功,状态码为:{},结果返回值为--{}.".format(api_id, api_name, status_code, res.text))
            else:
                Logger().error("第{}条用例-{}-执行失败!!!错误返回码:{}".format(api_id, api_name, status_code))
    return status_code, "请求方式错误"


def run_man():
    error_case = test_case_in_excel("API_Case.xlsx")
    if len(error_case) > 0:
        html = '接口自动化测试,共有 ' + str(len(error_case)) + ' 个异常接口,列表如下:' + '

' for test in error_case: html = html + '' send_email(html) # print(html) with open("report.html", "w") as f: f.write(html) else: Logger().info("本次测试,所有用例全部通过测试") send_email("本次测试,所有用例全部通过测试") # 读取配置文件 def get_conf(): config_path = os.path.join(os.getcwd(), 'config.yml') f = open(config_path, "r", encoding='utf-8') cfg = f.read() dt = yaml.load(cfg) sender = dt['email']['sender'] recevier = dt['email']['recevier'] smtpserver = dt['email']['smtpserver'] username = dt['email']['username'] password = dt['email']['password'] return sender, recevier, smtpserver, username, password # 发送QQ邮件 def send_email(text): today = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) sender, recevier, smtpserver, username, password = get_conf() subject = "[api_test]接口自动化测试结果通知{}".format(today) msg = MIMEText(text, 'html', 'utf-8') msg['Subject'] = subject msg['From'] = sender msg['To'] = recevier try: smtp = smtplib.SMTP_SSL(smtpserver, 465) smtp.login(sender, password) smtp.sendmail(sender, recevier, msg.as_string()) Logger().info("发送成功") except Exception as e: Logger().info("发送失败,因为:{}.".format(e)) # finally: # smtp.quit() if __name__ == '__main__': run_man()
封装的logging部分
# -*- coding:utf-8 -*-
import logging
import logging.handlers
import time


class Logger(object):
    def __init__(self):
        self.logger = logging.getLogger()   # 创建一个logger
        self.logger.setLevel(logging.INFO)  # logger等级总开关

        # 定义log的输出格式
        self.formatter = logging.Formatter(
            '[ %(asctime)s ]--%(threadName)-10s %(thread)d  line:%(lineno)d  [%(levelname)s] *** '
            '%(message)s',
            '%Y-%m-%d %H:%M:%S')

        '''创建一个handler输出到文件'''
        t = time.localtime()    # 获取本地时间
        now = time.strftime("%Y%m%d%H%M%S", t)  # 转化时间格式
        # self.logfile = logging.FileHandler('test_'+now+'.log', encoding='UTF-8')    # 以转换的时间来命名log文件
        # self.logfile.setFormatter(self.formatter)
        # self.logfile.setLevel(logging.INFO)
        # self.logger.addHandler(self.logfile)
        '''
        为了避免log文件中出现重复打印,在handler前面加一个判断,如果已经有handler了,则不再添加handler
        '''
        if not self.logger.handlers:
            self.logfile = logging.FileHandler('test_'+now+'.log', encoding='UTF-8')
            self.logfile.setFormatter(self.formatter)
            self.logfile.setLevel(logging.INFO)
            self.logger.addHandler(self.logfile)

        '''创建一个handler,用于输出到控制台'''
        self.consle = logging.StreamHandler()
        self.consle.setFormatter(self.formatter)
        self.consle.setLevel(logging.INFO)

        self.logger.addHandler(self.consle)

    '''
    1、使用removeHandler()方法可以解决重复打印的问题
    2、会出现重复打印的原因在于:logger封装好后,调用的时候,会根据getLogger(name)里的name获取同一个logger,但是这个logger里已经有了之前添加的handler了,
       再次调用时又会添加一个handler。所以,这个logger里面就会又多了一个handler,依次类推,调用几次就会有几个handler
    3、针对重复打印的问题,提出4个解决方法:(感谢强大的网民^-^)
    ① 每次创建不同name的logger,这样每次都是新的logger,就不会添加多个handler了(ps:该方法不推荐)
    ② 每次用完logger后,使用removeHandler()把logger里面的handler移除掉
    ③ 在使用logger之前,进行判断,如果已经有handler了,就不在添加了 (ps:推荐使用此种方法)
    ④ 每次使用完logger后,使用pop()把logger列表的handler移除
    '''
    # 日志的5个级别对应的5个函数
    def debug(self, msg):
        self.logger.debug(msg)
        self.logger.removeHandler(self.consle)

    def info(self, msg):
        self.logger.info(msg)
        self.logger.removeHandler(self.consle)

    def warn(self, msg):
        self.logger.warning(msg)
        self.logger.removeHandler(self.consle)

    def error(self, msg):
        self.logger.error(msg)
        self.logger.removeHandler(self.consle)

    def critical(self, msg):
        self.logger.critical(msg)
        self.logger.removeHandler(self.consle)


if __name__ == '__main__':
    log = Logger()
    log.info(12452)
    log.error('d4e4f52d12')
    # Logger('1').info(45454)
    # Logger('2').error(122)

你可能感兴趣的:(接口)

接口状态接口地址
' + test[0] + '' + test[1] + '' + test[2] + '