接口自动化实现及封装

一个简单的接口自动化场景:

用户登录商城-对id=4的商品进行支付购买-生成订单-查询该订单

# order.py
import requests
from requests.packages import urllib3

urllib3.disable_warnings()
# 购买商品生成订单

login_api = 'https://mall.jctesting.cn/api/account/login'
data_1 = {"account":"15200000001","password":"111111.","client":5}
res_1 = requests.post(url=login_api,json=data_1,verify=False)
login_res = res_1.json()
token = login_res['data']['token']
print(f"login_api:{login_res}")

header = {"token":token}

orderBuy_api = 'https://mall.jctesting.cn/api/order/buy'
data_2 = {"action":"submit","goods":[{"item_id":4,"num":1}],"use_integral":0,"address_id":"","remark":""}
res_2 = requests.post(url=orderBuy_api,json=data_2,headers=header,verify=False)
orderBuy_res = res_2.json()
order_id = orderBuy_res['data']['order_id']

pcPrepay_api = 'https://mall.jctesting.cn/api/payment/pcPrepay'
data_3 = {"order_id":order_id,"pay_way":3,"order_source":5}
res_3 = requests.post(url=pcPrepay_api, json=data_3, headers=header, verify=False)
pcPrepay_res = res_3.json()
print(pcPrepay_res)

orderList_api = 'https://mall.jctesting.cn/api/order/lists'
orderIdList = list()
res_4 = requests.get(url=orderList_api, headers=header, verify=False)
orderList_res = res_4.json()
print(orderList_res)
for order in orderList_res['data']['list']:
    orderIdList.append(order['id'])
    print(orderIdList)

把上述代码按【登录获取token】-【支付下单】-【查询订单】功能进行函数封装

# order.py
import requests
from requests.packages import urllib3

urllib3.disable_warnings()
# 购买商品生成订单

def get_token():
	login_api = 'https://mall.jctesting.cn/api/account/login'
	data_1 = {"account": "15200000001", "password": "111111.", "client": 5}
	res_1 = requests.post(url=login_api, json=data_1, verify=False)
	login_res = res_1.json()
	token = login_res['data']['token']
	print(f"login_api:{login_res}")
	return token

def payOrder(goodsId=4):
	token = get_token()
	orderBuy_api = 'https://mall.jctesting.cn/api/order/buy'
	header = {"token": token}
	data_2 = {"action":"submit","goods":[{"item_id":goodsId,"num":1}],"use_integral":0,"address_id":"","remark":""}
	res_2 = requests.post(url=orderBuy_api,json=data_2,headers=header,verify=False)
	orderBuy_res = res_2.json()
	order_id = orderBuy_res['data']['order_id']

	pcPrepay_api = 'https://mall.jctesting.cn/api/payment/pcPrepay'
	data_3 = {"order_id":order_id,"pay_way":3,"order_source":5}
	res_3 = requests.post(url=pcPrepay_api, json=data_3, headers=header, verify=False)
	pcPrepay_res = res_3.json()
	print(pcPrepay_res)
	return pcPrepay_res

def get_orderIdList():
	token = get_token()
	orderList_api = 'https://mall.jctesting.cn/api/order/lists'
	header = {"token": token}
	orderIdList = list()
	res_4 = requests.get(url=orderList_api, headers=header, verify=False)
	orderList_res = res_4.json()
	print(orderList_res)
	for order in orderList_res['data']['list']:
		orderIdList.append(order['id'])
	print(orderIdList)
	return orderIdList


if __name__ == '__main__':
	order = payOrder(4)
	orderIdList = get_orderIdList()

新建一个test_开头的py模块,编写测试用例(支付完成后的订单id是否在订单列表中)

# test_payOrder.py
import pytest

from pay_order import *

def test_pay_order():
	pay_order = payOrder(goodsId=4)
	order_id = pay_order['data']['order_id']
	order_idList = get_orderIdList()
	pytest.assume(order_id in order_idList)


if __name__ == '__main__':
	pytest.main(['-vs',test_pay_order()])

在这里,上述代码完成了一个最初始形态的接口场景式自动化,完成了用户进行通过余额进行下单支付生成订单的场景;

继续对order.py进行,封装,把该模块下所有关于订单的业务操作都规为一个类 class Order

# order.py
import requests
from requests.packages import urllib3

urllib3.disable_warnings()
class Order:
	def payOrder(self,goodsId=4):
		token = get_token()
		orderBuy_api = 'https://mall.jctesting.cn/api/order/buy'
		header = {"token": token}
		data_2 = {"action":"submit","goods":[{"item_id":goodsId,"num":1}],"use_integral":0,"address_id":"","remark":""}
		res_2 = requests.post(url=orderBuy_api,json=data_2,headers=header,verify=False)
		orderBuy_res = res_2.json()
		order_id = orderBuy_res['data']['order_id']

		pcPrepay_api = 'https://mall.jctesting.cn/api/payment/pcPrepay'
		data_3 = {"order_id":order_id,"pay_way":3,"order_source":5}
		res_3 = requests.post(url=pcPrepay_api, json=data_3, headers=header, verify=False)
		pcPrepay_res = res_3.json()
		print(pcPrepay_res)
		return pcPrepay_res


	def get_orderIdList(self):
		token = get_token()
		orderList_api = 'https://mall.jctesting.cn/api/order/lists'
		header = {"token": token}
		orderIdList = list()
		res_4 = requests.get(url=orderList_api, headers=header, verify=False)
		orderList_res = res_4.json()
		print(orderList_res)
		for order in orderList_res['data']['list']:
			orderIdList.append(order['id'])
		print(orderIdList)
		return orderIdList


if __name__ == '__main__':
	order = Order().payOrder(4)
	orderIdList = Order().get_orderIdList()

把 get_token( ) 放入 base_op.py中规为BaseOP类,BaseOP 类中主要存放所有业务模块类中需要重复用到的方法,BaseOP类可作为父类被其业务模块类继承

#base_op.py
import requests

class BaseOP:

	def get_token(self):
		login_api = 'https://mall.jctesting.cn/api/account/login'
		data_1 = {"account": "15200000001", "password": "111111.", "client": 5}
		res_1 = requests.post(url=login_api, json=data_1, verify=False)
		login_res = res_1.json()
		token = login_res['data']['token']
		print(f"login_api:{login_res}")
		return token
#order.py
import requests
from requests.packages import urllib3

from base_op import BaseOP

urllib3.disable_warnings()
# 购买商品生成订单
class Order(BaseOP):
	def payOrder(self,goodsId=4):
		token = self.get_token()
		orderBuy_api = 'https://mall.jctesting.cn/api/order/buy'
		header = {"token": token}
		data_2 = {"action":"submit","goods":[{"item_id":goodsId,"num":1}],"use_integral":0,"address_id":"","remark":""}
		res_2 = requests.post(url=orderBuy_api,json=data_2,headers=header,verify=False)
		orderBuy_res = res_2.json()
		order_id = orderBuy_res['data']['order_id']

		pcPrepay_api = 'https://mall.jctesting.cn/api/payment/pcPrepay'
		data_3 = {"order_id":order_id,"pay_way":3,"order_source":5}
		res_3 = requests.post(url=pcPrepay_api, json=data_3, headers=header, verify=False)
		pcPrepay_res = res_3.json()
		print(pcPrepay_res)
		return pcPrepay_res

	def get_orderIdList(self):
		token = self.get_token()
		orderList_api = 'https://mall.jctesting.cn/api/order/lists'
		header = {"token": token}
		orderIdList = list()
		res_4 = requests.get(url=orderList_api, headers=header, verify=False)
		orderList_res = res_4.json()
		print(orderList_res)
		for order in orderList_res['data']['list']:
			orderIdList.append(order['id'])
		print(orderIdList)
		return orderIdList


if __name__ == '__main__':
	order = Order().payOrder(4)
	orderIdList = Order().get_orderIdList()

order.py模块中 Order类通过继承BaseOP,通过self.get_token()直接调用BaseOP类中的get_token方法获取token参数;

接下来把order.py模块中的接口请求相关代码独立出来,放入py_page文件夹中,在py_page中新建一个base_api.py模块存放接口请求的封装

#py_page/base_api.py
import os
import traceback
import requests
import yaml
from requests.packages import urllib3
from string import Template


from common.log import Logger

logging = Logger(__name__).get_logger()
class BaseApi:
    urllib3.disable_warnings()
    def request_api(self,method, api, params=None, data=None, json=None, headers=None, res_type='json', verify=False)->dict:
        '''
        基于requests的接口请求封装
        :param method: 接口请求方式get、post
        :param api: 接口请求地址
        :param params: 用于get请求中传入请求数据,接收一个字典类型
        :param data: 用于post请求中传入表单数据,接收一个字典类型
        :param json: post请求中用于传入json格式的请求数据,接收一个字典类型
        :param headers: 用于构造请求头信息,接收一个字典类型
        :param res_type: 指定响应报文类型json、text
        :param verify: 接收布尔值,默认False,即不对https请求SSL验证
        :return: 接口响应状态码,以及接口响应报文,返回一个字典类型
        '''
        host = "https://mall.jctesting.cn"
        api_url = host+api
        res_dict = dict()
        if method == 'get':
            try:
                res = requests.get(url=api_url, params=params, headers=headers, verify=verify)
                if res_type == 'json':
                    res_content = res.json()
                else:
                    res_content = res.text
                res_dict['status_code'] = res.status_code
                res_dict['content'] = res_content
                return res_dict
            except Exception as e:
                error_info = traceback.format_exc()
                logging.error(f"接口::{api}::请求出错::{e},错误详情:{error_info}")

        elif method == 'post':
            try:
                res = requests.post(url=api_url, data=data, json=json, headers=headers, verify=verify)
                if res_type == 'json':
                    res_content = res.json()
                else:
                    res_content = res.text
                res_dict['status_code'] = res.status_code
                res_dict['content'] = res_content
                return res_dict
            except Exception as e:
                error_info = traceback.format_exc()
                logging.error(f"接口::{api}::请求出错::{e},错误详情:{error_info}")


    def run_requests(self,yaml_path,api_name,**kwargs):
        '''
        用于读取yaml_api中的接口数据,实现接口请求
        :param yaml_path: yaml文件路径
        :param api_name: yaml文件中的接口名称
        :param kwargs: 发起接口请求的自定义参数,即实现从外部传入参数到请求体中
        :return: 接口响应状态码,以及接口响应报文,返回一个字典类型
        '''
        try:
            with open(yaml_path,"r",encoding="UTF-8") as f:
                yaml_datas = yaml.safe_load(f)  # 获取整个yaml全部数据,返回一个字典对象
                dic_api_datas = yaml_datas[f'{api_name}'] # 根据接口名字获取该接口的请求数据,返回一个字典对象
                yaml_api_datas = yaml.dump(dic_api_datas) # 把接口的请求数据转换成yaml格式,返回一个字符串对象
                for k in kwargs.keys(): # 对传入参数数据类型为字符串的进行处理,防止模板匹配与yaml转换被转换成非字符串类型
                    if isinstance(kwargs[k], str):
                        kwargs[k] = f"'{kwargs[k]}'"
                temp_api_datas = Template(yaml_api_datas).substitute(kwargs) # 通过Template模板,把字典对象kwargs作为参数传入yaml_api_datas中
                api_datas = yaml.safe_load(temp_api_datas) # 把匹配到参数的yaml格式字符串对象再次通过yaml加载的方式转换成一个字典对象
                print(f"api_datas:{api_datas}")
                logging.info(f"读取YAML文件::{yaml_path}::请求的接口名称::{api_name}::传入接口参数::{kwargs}::接口请求参数api_datas::{api_datas}")
                request = self.request_api(method=api_datas['method'],api=api_datas['api'],headers=api_datas['headers'],
                                           params=api_datas['params'],data=api_datas['data'],json=api_datas['json'])
                logging.info(f"接口::{api_datas['api']}::响应数据为::{request}")
                return request
        except Exception as e:
            error_info = traceback.format_exc()
            logging.error(f"读取YAML文件:{yaml_path},并发起接口请求过程中出错:{e}::错误详情:{error_info}")

    @classmethod
    def get_path(cls,relative_path):
        '''
        文件的路径拼接,返回一个绝对路径
        :param relative_path: 文件的相对路径
        :return: 文件的绝对路径
        '''
        file_path = os.path.abspath(__file__)
        dir_path = os.path.split(file_path)[0]
        far_path = os.path.dirname(dir_path)
        path = far_path + relative_path
        return path

把接口请求数据独立出来,通过yaml_api文件夹独立存放,通过BaseApi中的run_requests,读取yaml文件获取接口数据,传入request_api( )中完成接口请求

#yaml_api/confirmOrder_page.yaml
confirmOrder_buy: # 确认订单页面提交订单接口
  api: /api/order/buy
  method: post
  headers: {"token":$token}
  params: ~
  data: ~
  json: {"action":"submit","goods":[{"item_id":$goodsId,"num":1}],"use_integral":0,"address_id":"","remark":""}

 payment_pcPrepay: # 确认订单支付接口(pay_way:3 余额支付)
  api: /api/payment/pcPrepay
  method: post
  headers: { "token": $token }
  params: ~
  data: ~
  json: {"order_id":$order_id,"pay_way":$pay_way,"order_source":5}

YAML文件中的$为变量占位符,在run_requests( )方法中首先读取YAML中的数据,然后把传入的参数通过Template方法完成对$变量的匹配成有效的请求参数,传入到request_api方法中发起接口请求;这样就形成了接口数据与请求代码相互分离的基础请求封装;

在py_page文件夹内,继续按页面新建py模块,每一个py模块就是一个页面类,这个类中包含该页面的所有可请求的接口,每个接口封装成独立的方法,这样就形成了页面接口层,每个类都继承BaseApi,实现对run_requests方法的调用完成接口的请求封装;

#py_page/confirmOrder_page.py
from py_page.base_api import BaseApi
from py_page.registerLogin_page import RegisterLoginPageApi


class ConfirmOrderPageApi(BaseApi):

	path = BaseApi.get_path('/yaml_api/confirmOrder_page.yaml')
	token = RegisterLoginPageApi().get_token()

	# 新增收货人信息
	def add_address(self,name,phone,address):
		add_address_res = self.run_requests(self.path,'addAddress', token=self.token,name=name,phone=phone,address=address)
		return add_address_res

	# 确认并提交订单
	def confirmOrder_buy(self,itemId):
		confirmOrder_buy_res = self.run_requests(self.path, 'confirmOrder_buy', token=self.token, itemId=itemId)
		return confirmOrder_buy_res




if __name__ == '__main__':
	pass

现在修改base_op.py与order.py,并把这两个模块放入operation文件夹中形成业务逻辑层,通过调用py_page中的页面接口方法实现对应业务逻辑,实现业务与接口的分层方便接口复用;

因为需要调用登录接口实现用户登录因此,在修改base_op.py前按照PO分层思想,商城登录是一个单独的页面,因此在yaml_api中新建一个registerlogin_page.yaml存放登录和注册接口信息;

#yaml_api/registerlogin_page.yaml
login:
  api: /api/account/login
  method: post
  headers: ~
  params: ~
  data: ~
  json: {"account": $account,"password": $password,"client": 5}

register:
  api: /api/account/register
  method: post
  headers: ~
  params: ~
  data: ~
  json: {"mobile":$mobile,"password":$password,"code":"","client":5}

在py_page中新建一个registerLogin_page.py注册登录页面接口调用模块

#py_page/registerLogin_page.py
from py_page.base_api import BaseApi

class RegisterLoginPageApi(BaseApi):

	def __init__(self,path="./yaml_api/registerlogin_page.yaml",token=None):
		self.path = path
		self.token = token

	def login_api(self,account,password):
		login = self.run_requests(self.path,'login', account=account,password=password)
		return login

	def register_api(self,mobile,password):
		register = self.run_requests(self.path,'register', mobile=mobile,password=password)
		return register

在operation业务层新建registerLogin.py实现登录注册功能

#operation/registerLogin.py
from py_page.registerLogin_page import RegisterLoginPageApi

class RegisterLogin:

	def login(self,account,password):
		login_res = RegisterLoginPageApi().login_api(account,password)
		return login_res


	def register(self,mobile,password):
		register_res = RegisterLoginPageApi().register_api(mobile,password)
		return register_res

小结一下从接口数据-业务实现:yaml_api -> py_page -> operation 即对应接口数据层,页面接口层,业务逻辑层;

因此在base_op.py实现登录BaseOP修改如下

#operation/base_op.py
from operation.registerLogin import RegisterLogin

class BaseOP:

	def get_token(self):
		login = RegisterLogin().login(account='15200000001', password='111111.')
		token = login['content']['data']['token']
		return token

同样在order.py中因为需要调用订单列表接口实现获取订单,按照分层思想 yaml_api -> py_page -> operation构建数据与方法

在yaml_api中新建orderList_page.yaml

#yaml_api/orderList_page.yaml
order_list: # 订单列表
  api: /api/order/lists
  method: get
  headers: {"token":$token}
  params: ~
  data: ~
  json: ~

在py_page中新建orderList_page.py

#py_page/orderList_page.py
from py_page.base_api import BaseApi

class OrderListApi(BaseApi):

	def __init__(self,path="./yaml_api/orderList_page.yaml",token=None):
		self.path = path
		self.token = token

	def orderList_api(self):
		orderList = self.run_requests(self.path,'order_list', token=self.token)
		return orderList

在operation中order.py实现业务实现如下

#operation/order.py
from requests.packages import urllib3

from operation.base_op import BaseOP
from py_page.confirmOrder_page import ConfirmOrderPageApi
from py_page.orderList_page import OrderListApi

urllib3.disable_warnings()
# 购买商品生成订单
class Order(BaseOP):

	def __init__(self):
		self.token = self.get_token()

	def payOrder(self,goodsId=4,pay_way=3):
		# 提交订单
		res_1 = ConfirmOrderPageApi(token=self.token).confirmOrder_buy_api(goodsId)
		order_id = res_1['content']['data']['order_id']
		# 选择支付方式进行支付
		ConfirmOrderPageApi(token=self.token).payment_pcPrepay_api(order_id=order_id,pay_way=pay_way)
		# 支付完成返回订单详情
		res_2 = ConfirmOrderPageApi(token=self.token).confirmOrder_detail_api(id=order_id)
		return res_2


	def get_orderIdList(self):
		id_list = list()
		res = OrderListApi(token=self.token).orderList_api()
		data_list = res['content']['data']['list']
		for order in data_list:
			id_list.append(order['id'])
		return id_list

到此,场景:用户登录商城-对id=4的商品进行支付购买-生成订单-查询该订单 已经按PO设计模式形成了代码分层;

接下来基于该分层基础上,直接对operation(业务功能)调用实现测试用例编写;

新建test文件夹,在该文件夹下新建以test_开头的测试模块,在模块中按类编写测试用例

#test/test_payOrder.py
import pytest

from operation.order import Order

class TestOrder:
	def test_pay_order(self):
		pay_order = Order().payOrder(goodsId=4,pay_way=3)
		order_id = pay_order['data']['order_id']
		order_idList = Order().get_orderIdList()
		pytest.assume(order_id in order_idList)

目录结构

为框架增加日志

在项目根目录增加log.py模块

import logging
import time
import os

class Logger:
    def __init__(self,name,CmdLevel=logging.INFO, FileLevel=logging.INFO):
        # 创建一个 logger
        self.logger = logging.getLogger(name)
        # 设置Log等级总开关
        self.logger.setLevel(CmdLevel)
        # 创建 handler,用于日志写入
        currTime = time.strftime("%Y-%m-%d")
        log_path = os.path.abspath(os.path.join(os.getcwd(), "")) + '/Logs/'
        log_name = log_path + currTime + '.log'
        fh = logging.FileHandler(log_name, mode='a')  # mode = 'a' 为在原日志上追加,'w'为覆盖 输出日志到文件
        fh.setLevel(FileLevel) # 输出到 file 的日志等级
        ch = logging.StreamHandler() # 输出日志到控制台
        ch.setLevel(FileLevel) # 输出到控制台的日志等级
        # 定义日志handler的输出格式
        fmt = logging.Formatter('%(asctime)s - %(filename)s:[%(lineno)s] - [%(levelname)s] - %(message)s')
        fh.setFormatter(fmt)
        ch.setFormatter(fmt)
        # 添加 logger 到 handler里面
        self.logger.addHandler(fh)
        self.logger.addHandler(ch)

在base_api.py模块中引入日志,把每个接口请求及响应都以日志info形式记录到项目根目录Logs文件夹(注:在项目根目录下创建Logs文件夹)

import yaml
from requests.packages import urllib3
import json
from string import Template

from log import Logger

logging = Logger(__name__)
class BaseApi:
    urllib3.disable_warnings()
    def request_api(self,method=None,api=None,params=None,data=None,json=None,headers=None,res_type='json',verify=False,**kwargs)->dict:
        host = "https://mall.jctesting.cn"
        api_url = host+api
        res_dict = dict()
        if method == 'get':
            try:
                res = requests.get(url=api_url,params=params,headers=headers,verify=verify)
                if res_type == 'json':
                    res_content = res.json()
                else:
                    res_content = res.text
                res_dict['status_code'] = res.status_code
                res_dict['content'] = res_content
                return res_dict
            except Exception as e:
                logging.logger.error(f"接口::{api}::请求出错::{e}")

        elif method == 'post':
            try:
                res = requests.post(url=api_url,data=data,json=json,headers=headers,verify=verify)
                if res_type == 'json':
                    res_content = res.json()
                else:
                    res_content = res.text
                res_dict['status_code'] = res.status_code
                res_dict['content'] = res_content
                return res_dict
            except Exception as e:
                logging.logger.error(f"接口::{api}::请求出错::{e}")


    def run_requests(self,yaml_path,api_name,**kwargs):
        try:
            with open(yaml_path,"r",encoding="UTF-8") as f:
                yaml_datas = yaml.safe_load(f)
                datas = yaml_datas[f'{api_name}']
                datas_str = json.dumps(datas)
                datas_str_var = Template(datas_str).substitute(kwargs)
                api_datas = json.loads(datas_str_var)
                logging.logger.info(f"读取YAML文件::{yaml_path}::请求的接口名称::{api_name}::传入接口参数::{kwargs}::接口请求参数api_datas::{api_datas}")
                request = self.request_api(method=api_datas['method'],api=api_datas['api'],headers=api_datas['headers'],
                                           params=api_datas['params'],data=api_datas['data'],json=api_datas['json'])
                logging.logger.info(f"接口::{api_datas['api']}::响应数据为::{request}")
                return request
        except Exception as e:
            logging.logger.error(f"读取YAML文件::{yaml_path}出错::{e}")

在测试用例中引用allure和log

import pytest
import allure
from log import Logger

from operation.order import Order

logging = Logger(__name__)
@allure.feature("测试订单逻辑")
class TestOrder:
	@allure.story("测试生成订单")
	@allure.title("用余额支付生成订单")
	def test_pay_order(self,goodsId = 4,pay_way=3):
		with allure.step(f"对商品:{goodsId}进行下单支付"):
			pay_order = Order().payOrder(goodsId=goodsId,pay_way=pay_way)
		with allure.step("获取订单ID"):
			order_id = pay_order['content']['data']['id']
		order_idList = Order().get_orderIdList()
		pytest.assume(order_id in order_idList)


if __name__ == '__main__':
	pytest.main(['-vs','test_apyOrder.py::test_pay_order()'])

在测试框架中引入邮件模块

在项目根目录下新建一个common文件夹用于存放send_mail.py邮件模块

#common/send_mail.py
import smtplib
from email.header import Header
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

class SendEmail:

    # 初始化服务器信息
    def __init__(self,mail_host, mail_user, mail_pass, sender, receives):
        self.mail_host = mail_host
        self.mail_user = mail_user
        self.mail_pass = mail_pass
        self.sender = sender
        self.receivers = receives

    # 以文本的形式发送邮件
    def make_email_by_text(self,context, subject, from_address, to_address):
        message = MIMEText(context, 'plain', 'UTF-8')
        message['Subject'] = subject  # 邮件标题
        message['From'] = Header(from_address, "utf-8")  # 邮件主体中发送者名称
        message['To'] = Header(to_address, "utf-8")  # 邮件主体中接收者名称
        self.send_email(message)

    # 以文本和附件的形式发送邮件
    def make_email_by_att(self,content,file_path,subject, from_address, to_address):
        message = MIMEMultipart()
        message['Subject'] = subject  # 邮件标题
        message['From'] = Header(from_address, "utf-8")  # 邮件主体中发送者名称
        message['To'] = Header(to_address, "utf-8")  # 邮件主体中接收者名称
        body = MIMEText(content,'plain','utf-8')
        message.attach(body)
        att = MIMEApplication(open(file_path, 'rb').read())
        att.add_header('Content-Disposition','attachment',filename='allure测试报告.zip')
        message.attach(att)
        self.send_email(message)

    # 登录并进行发送
    def send_email(self,message):
        # 进行登录发送
        try:
            smtpobj = smtplib.SMTP_SSL(self.mail_host)
            smtpobj.ehlo(self.mail_host)
            smtpobj.login(self.mail_user,self.mail_pass)
            smtpobj.sendmail(self.sender,self.receivers,message.as_string())
            smtpobj.quit()
            print('success')
        except Exception as e:
            print(f'error: {e}')
            raise e

由于allure测试报告是一个包含html, js, css 等多种文件的文件夹,要实现测试执行后把测试报告通过邮件打包发送,这里需要在common文件夹下封装一个zip_file.py模块实现对文件夹的封装

import os
import zipfile
from log import Logger

logging = Logger(__name__)
def zip_files(dir_path,zip_path):
	'''
	:param dir_path: 需要压缩的文件夹地址  例:'D:\Buyer_test_code\apipotest_jcmall\report'
	:param zip_path: 压缩后的文件存在地址+压缩文件名  例:'D:\Buyer_test_code\apipotest_jcmall\report_zip\report.zip'
	:return: 0  压缩失败  1 压缩成功
	'''
	try:
		with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as f:
			for dirpath, dirnames, filenames in os.walk(dir_path):
				fpath = dirpath.replace(dir_path, '')
				fpath = fpath and fpath + os.sep or ''
				for filename in filenames:
					f.write(os.path.join(dirpath, filename), fpath+filename)
			logging.logger.info(f"文件:{dir_path} 压缩成功")
	except Exception as e:
		logging.logger.error(f"文件:{dir_path} 压缩失败,错误:{e}")
		return 0
	else:
		return 1

在项目根目录下新建一个程序执行模块run.py,这个模块的主要功能实现:

1、执行命令:python -m pytest ./test/ -s -q --alluredir=./result/ 执行测试用例生成allure测试结果;

2、在allure测试结果基础上生成测试报告:allure generate ./result/ -o ./report/ --clean;

3、调用zip_file.py模块对allure测试报告进行压缩打包,生成 report.zip;

4、进行邮件发送,把测试结果压缩包作为附件进行打包发送;

import os
import yaml
from common.send_mail import SendEmail
from log import Logger
from common.zip_file import zip_files

logging = Logger(__name__)
def run_testCase():
	test_allure = "python -m pytest ./test/ -s -q --alluredir=./result/"
	logging.logger.info(f"run_testCase::正在执行测试命令:{test_allure}")
	try:
		os.system(test_allure)
	except Exception as e:
		logging.logger.error(f"os执行命令{test_allure}错误:{e}")
	else:
		make_report = "allure generate ./result/ -o ./report/ --clean"
		try:
			logging.logger.info(f"run_testCase::测试执行完毕,正在执行生成报告命令:{make_report}")
			os.system(make_report)
		except Exception as e:
			logging.logger.error(f"os执行命令{make_report}错误:{e}")
		else:
			dir_path = r'./report'
			zip_path = r'./report_zip/report.zip'
			zip_report = zip_files(dir_path,zip_path)
			if zip_report:
				file_name = r'./config.yaml'
				file_path = zip_path
				content = "测试执行结束,Allure测试报告在邮件附件中,请使用Pycharm打开查看。"
				Subject = '接口场景式自动化测试报告'  # 邮件标题
				From = "Jie的QQ邮箱"  # 邮件主体中发送者名称
				To = "Jie的163邮箱"  # 邮件主体中接收者名称
				try:
					with open (file_name,"r",encoding="UTF-8") as f:
						config = yaml.safe_load(f)
						config_email = config["Email"]
					send = SendEmail(mail_host=config_email['mail_host'], mail_user=config_email['mail_user'], mail_pass=config_email['mail_pass'],
								 sender=config_email['sender'], receives=config_email['receives'])
					send.make_email_by_att(content,file_path, Subject, From, To)
					logging.logger.info(f"邮件发送成功,邮件标题:{Subject}")
				except Exception as e:
					logging.logger.error(f"读取配置文件::{file_name}::失败::{e}")
					raise e

run_testCase()

到此一个接口自动化测试框架基本完成

完整目录结构如下

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐