python+pytest接口自动化框架搭建

一、Pycharm中创建项目结构

1.新建一个工程testProject
2.在工程的根目录下新建一个conftest.py(测试用例的一些fixture配置)和pytest.ini(改变pytest的运行方式)
3.在工程下创建以下package包和文件夹
python+pytest接口自动化框架搭建_第1张图片

  • common:这个包放一些公共的方法,如:读取excel文件方法,读取mysql、oracle的脚本
  • config:放一些配置文件,如邮箱的一些参数:收件人,发件人,密码等
  • logs:这里存放日志信息
  • report:这里存放测试报告
  • test_case:这个包放test开头的测试用例,也可以放一些非test开头的封装接口方法
    注意:
    每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。

二、在common包下实现一些常用工具类

1)常用的logger日志类封装
# -*- coding:utf-8 -*-

#默认日志格式
DEFAULT_LOG_FMT='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s: %(message)s'
#默认时间格式
DEFUALT_LOG_DATEFMT='%Y-%m-%d %H:%M:%S'
#输出日志路径
import os
LOG_OUT_PATH=os.path.abspath('..')+'/logs/'

import sys
import logging
from time import strftime

class Logger(object):
	def __init__(self):
		self._logger=logging.getLogger()
		self.DEFAULT_LOG_FILENAME='{0}{1}.log'.format(LOG_OUT_PATH,strftime("%Y-%m-%d"))
		self.formatter=logging.Formatter(fmt=DEFAULT_LOG_FMT,datefmt=DEFUALT_LOG_DATEFMT)
		self._logger.addHandler(self._get_file_handler(self.DEFAULT_LOG_FILENAME))
		self._logger.addHandler(self._get_console_handler())
		self._logger.setLevel(logging.INFO) #默认等级

	def _get_file_handler(self,filename):
		filehandler=logging.FileHandler(filename,encoding="utf-8")
		filehandler.setFormatter(self.formatter)
		return  filehandler

	def _get_console_handler(self):
		console_handler=logging.StreamHandler(sys.stdout)
		console_handler.setFormatter(self.formatter)
		return console_handler

	@property
	def logger(self):
		return self._logger

if __name__ == '__main__':
	import datetime
	logging=Logger().logger
	logging.info(u"{}:开始XXX操作".format(datetime.datetime.now()))

2)常用的readConfig类封装
# -*- coding:utf-8 -*-
import configparser


class ReadConfig(object):
	# 构造函数
	def __init__(self, file_name=None):
		'''
		:param file_name: 配置文件地址
		:param node: 节点名字
		'''
		# 容错处理
		if file_name == None:
			# 默认地址
			self.file_name = r'../config/config.ini'
		else:
			self.file_name = file_name
		self.cf = self.load_ini(self.file_name)

	# 加载文件
	def load_ini(self, file_name):
		cf = configparser.ConfigParser()
		cf.read(file_name, encoding='utf-8')
		return cf

	# 获取value得值
	def get_value(self, node, key):
		data = self.cf.get(node, key)
		return data

	# 存储token值到config。ini
	def set_value(self, group, instance, value):
		self.cf.set(group, instance, value)

	# 保存config.ini
	def save(self,filename):
		with open(filename, 'w') as f:
			self.cf.write(f)


if __name__ == '__main__':
	cf = ReadConfig()
	print(cf.get_value('env', 'ip'))

config.ini文件格式:

# cat ../config/config.ini
[db]
user = root
password = 123456
host = dev.mysql.com
port = 3306

[redis]
host = dev.redis.com
port = 6379
password = 123456
3)常用的读取Mysql操作封装
# -*- coding:utf-8 -*-
import pymysql
import sys
class Mc(object):
	#  这里 注释连接的方法,是为了 实例化对象时,就创建连接。不许要单独处理连接了。
	#
	# def connDataBase(self):
	#     ''' 数据库连接 '''
	#
	#     self.db = pymysql.connect(self.host,self.username,self.password,self.port,self.database)
	#
	#     # self.cursor = self.db.cursor()
	#
	#     return self.db
	def __init__(self,db_host="*****", username="root", pw="**********",port=3306, dbname="*****"):
		self.db_host=db_host
		self.username=username
		self.pw=pw
		self.dbname=dbname
		self.port=port
		self.db=pymysql.connect(self.db_host,self.username,self.pw,self.dbname,self.port,charset='utf8')
		#print(dbname)

	def insertDB(self, sql):
		''' 插入数据库操作 '''

		self.cursor = self.db.cursor()

		try:
			# 执行sql
			self.cursor.execute(sql)
			# tt = self.cursor.execute(sql)  # 返回 插入数据 条数 可以根据 返回值 判定处理结果
			# print(tt)
			self.db.commit()
		except:
			# 发生错误时回滚
			self.db.rollback()
		finally:
			self.cursor.close()

	def deleteDB(self, sql):
		''' 操作数据库数据删除 '''
		self.cursor = self.db.cursor()

		try:
			# 执行sql
			self.cursor.execute(sql)
			# tt = self.cursor.execute(sql) # 返回 删除数据 条数 可以根据 返回值 判定处理结果
			# print(tt)
			self.db.commit()
		except:
			# 发生错误时回滚
			self.db.rollback()
		finally:
			self.cursor.close()

	def updateDb(self, sql):
		''' 更新数据库操作 '''

		self.cursor = self.db.cursor()

		try:
			# 执行sql
			self.cursor.execute(sql)
			# tt = self.cursor.execute(sql) # 返回 更新数据 条数 可以根据 返回值 判定处理结果
			# print(tt)
			self.db.commit()
		except:
			# 发生错误时回滚
			self.db.rollback()
		finally:
			self.cursor.close()

	def selectDb(self, sql):
		''' 数据库查询 '''
		self.cursor = self.db.cursor()
		try:
			self.cursor.execute(sql)  # 返回 查询数据 条数 可以根据 返回值 判定处理结果

			data = self.cursor.fetchall()  # 返回所有记录列表
			#print(type(data))
			#print(data[0][0])
		except:
			print('Error: unable to fecth data')
		finally:
			self.cursor.close()
		return data
	def closeDb(self):
		''' 数据库连接关闭 '''
		self.db.close()


if __name__ == '__main__':
	mc=Mc()
	sql='SELECT * from student;'
	result=mc.selectDb(sql)
	print(result)

注意:
连接数据库时报错“AttributeError: ‘NoneType’ object has no attribute ‘encoding’”
解决办法:将下面部分的utf-8改为utf8
self.db=pymysql.connect(self.db_host,self.username,self.pw,self.dbname,self.port,charset=‘utf-8’)

4)操作Excel类封装

在自动化测试中经常将测试数据放入到Excel中,为了操作简便,将一些操作封装到readExcel类中

# -*- coding:utf-8 -*-
# author_='ting.chun'
# date:2020/10/22 19:05
import xlrd
from xlutils import copy
class ReadExcel:
	'''
	此类专门用于读取Excel
	'''
	#__workbook用于存放excel文件的对象
	__workbook=None
	__sheet=None
	__print=False #用于开启全局打印
	def __init__(self,file_name,sheet_name):
		'''
		类的初始化方法,在类似初始化的时候被调用
		:param file_name: excel的文件名
		:param sheet_name: excel中需要访问的sheet名
		'''
		self.file_name=file_name
		ReadExcel.__workbook=xlrd.open_workbook(file_name)
		#根据sheet名称获取sheet内容
		ReadExcel.__sheet=ReadExcel.__workbook.sheet_by_name(sheet_name)
	def get_number_of_rows(self):
		'''
		 获取表格中内容的最大行数
		:return:__number_of_row
		'''
		__rows_number=self.__sheet.nrows
		if ReadExcel.__print is True:
			print(__rows_number)
		return __rows_number

	def get_number_of_cols(self):
		'''
		获取表格中内容最大的列数
		:return: __number_of_cols
		'''
		__cols_number=self.__sheet.ncols
		if ReadExcel.__print is True:
			print(__cols_number)
		return __cols_number

	def get_value_of_row(self,row_index):
		"""
		获取某一行的所有值构成列表
		:param row_index: 行号
		:return: 行内容组成的列表
		"""
		__row_value=self.__sheet.row_values(row_index)
		if ReadExcel.__print is True:
			print(__row_value)
		return __row_value

	def get_value_of_col(self, col_index):
		"""
		获取某一列的所有值构成的列表
		:param col_index: 列号
		:return: 列中内容组成的列表
		"""
		__col_value = self.__sheet.col_values(col_index)
		if ReadExcel.__print is True:
			print(__col_value)
		return __col_value

	def get_value_of_cell(self, row_index, col_index):
		"""
		获取某一个单元格中的值
		:param row_index: 行号
		:param col_index: 列号
		:return: 单元格中的内容
		"""
		# 第row_index行 col_index列是内容
		__cell_value = self.__sheet.cell(row_index, col_index).value
		if ReadExcel.__print is True:
			print(__cell_value)
		return __cell_value

	def write(self,sheet,row,col,value):
		new_workbook=copy.copy(ReadExcel.__workbook)
		w_sheet=new_workbook.get_sheet(sheet)
		w_sheet.write(row,col,value)
		new_workbook.save(self.file_name) #保存文件

if __name__=='__main__':
	filename=r'..\config\api_info.xls'
	el=ReadExcel(filename,'Sheet1')
	el.write(0,2,1,"你好吗")

5)tool工具代码介绍
# -*- coding:utf-8 -*-
# author_='ting.chun'
# date:2020/10/22 11:26
import time,datetime
import random
import string
class Tool:
	#获取注册的email
	def get_email(self):
		value='{}@163.com'.format(str(int(time.time())))
		return value
	#使用时间戳作为一个唯一标识
	def get_unique(self):
		unique=int(time.time())
		return unique
	#生成11的手机号
	def get_phone(self):
		# 运营商的号码前缀
		prefix = [
			'130', '131', '132', '133', '134', '135', '136', '137', '138', '139',
			'145', '147', '149', '150', '151', '152', '153', '155', '156', '157',
			'158', '159', '165', '171', '172', '173', '174', '175', '176', '177',
			'178', '180', '181', '182', '183', '184', '185', '186', '187', '188',
			'189', '191'
		]
		#随机取一个手机号前缀
		pos=random.randint(0,len(prefix)-1)
		#随机生成后8为数字,string。digits是数字0到9
		suffix=''.join(random.sample(string.digits,8))
		#拼接返回11位手机号
		return prefix[pos]+suffix


#print(str(int(time.time()))+"@163.com")
if __name__ == '__main__':
	tool=Tool()
	phone=tool.get_email()
	print(phone)
6)testcase
import pytest
from interface.regexEngine import regex_Engine
from common.readConfig import ReadConfig
import time
import os
class Test_Rengine:
	#类级别
	def setup_class(self):
		#先删除文件
        path=r'C:\User****'
        file_list =os.listdir(path)
        for file in file_list :
        	os.remove(os.path.join(path,file))
        print("在该类执行之前,先引入配置项参数")
        cf = ReadConfig(r'../config/reg.ini')  # 获取配置文件
        url=cf.get_value('env','url')
        self.reg=regex_Engine(r'../data/rengine.xlsx',url)
              
    def test_counts(self):
    	for i in range(11):
        	res=self.reg.set_regex(i)
            if res==1:
            	print("用例%s执行成功" %(i))
            else:
            	print("用例%s执行失败" %(i))

	 @pytest.mark.skip
     # 类级别
     def teardown_class(self):
         print("执行结果写入------------------------------")
         filename = r'../data/result.xlsx'
         self.reg.get_data(filename)
         print("----->test_Regine模块用例执行完成")           
if __name__ == "__main__":
         pytest.main()

上述主要受common包组成模块代码,后续会继续更新,喜欢的请关注哟!

你可能感兴趣的:(自动化测试,1024程序员节,python)