当前位置:   article > 正文

Python从无到有搭建接口(API)自动化测试框架_python接口自动化框架搭建

python接口自动化框架搭建

目录

1、前言

2、思路    

3、正文

一、路径模块-initPath.py

二、配置文件模块-getConfig.py

三、读取用例模块-getCase.py

四、数据库操作模块-operatorDB.py

五、日志模块-log.py

六、邮件模块-sendEmail.py

七、消息模块-sendMsg.py

八、变量参数化模块-parameteriZation.py

九、API请求模块-sendApirequests.py

十、公共方法的模块(核心)-commKeyword.py

十一、工厂封装(核心)-methodFactoy.py

十二、解析用例(核心)-testCase.py

十三、最后的运行文件-testRun.py


1、前言

         自动化测试,是测试道路上不可或缺的重要部分,现在有很多自动测试工具,基本可以满足软件市场的测试要求,但使用工具让人知其然而不知其所以然,学会了也只是一个自动化测试工具人,所以学会自动化框架,是摆脱工具人、提升自己、加薪升职的必经之路;

         天王刘德华说:学到了要教人。

         这是一个已经学会了的人分享的一点知识,希望测试同胞们在前进路上路过我这篇博客时,如得感觉有一点点的帮助,请留下您的一个赞。

         观看此文时,需要有入门级的Python代码基础,如没有,请先去找视频教程学习一段时间。

         本文所有讲解代码与执行结果用截图展示,这为了是让您看着可以有个写代码的过程,提升自己;当然如果不想写,也可以只接跳转每一小节末尾,整体代码展示,可以"使用程序员高阶技能ctrl+c, ctrl+v"自行学习。

        源码包:Python_requests_api自动化测试框架-Python文档类资源-CSDN下载

        为了大家能自己去敲代码,而不是拿来主义,源码设置了一点点利益限制。

2、思路    

       1、搭建一个目录框架

             如下图

     

            common目录里内容含义

             setApirequest.py  实现API接口请求的模块,实现发送post/get等方法;      

             getCase.py  实现读取data目录里的用例文件数据的,设计执行格式,输出JSON用例集;

             getConfig.py 实现读取config目录下的配置文件;

             initPath.py  实现获取框架文件路径,方便其他模块使用目录方法;

             log.py  实现日志打印统一入口,将文件输出到log目录里;

             operatorDB.py  实现读写数据的方法;

             parameteriZation.py  实现数据参数化的实体类;

             sendEmail.py\ SendMsg.py  实现实时发送测试报告至邮件与企业微信的方法;

            kemel目录里内容含义

             methodFactory.py 实现各方法调用的统一入口;

             commKeyworl.py 公共方法、主要是分装所有底层模块的操作入口,并提供一些特殊性的公共方法;

            testcase目录里内容含义

             图片里目录中没有添加文件。应该是这个文件tsetCase.py,实现解析getCase.py里输出的JSON用例集,执行用例的,检查执行结果的模块,因为Unitest库的要求,此用例解析目录与文件必须以test开头,当然含义也见名思义。

           其他目录 

            data用例文件目录,log输出日志目录、report输出测试报告目录,library引入三方模块目录(ddt数据驱动模块,HTMLRunner测试报告格式化输出模块)

             library引入两个三方库的下载路径(重要文件,不或或缺):

             ddt:自动代测试数据驱动ddt.py-互联网文档类资源-CSDN下载

             HTMLRunner:   HTMLTestRunnerNew.py针对《Python从无到有搭建接口(API)自动化测试框架》的测试报告,非此框架勿下载_htmltestrunnernew-互联网文档类资源-CSDN下载

   

       5、分层概念

           一个软件MCV的层次概念

            M是底层,模型层,封装功能的代码,属于底层代码 。

            C是中层,控制层,封装所有底层的接入方法。

            V是高层,会话层,运行执行等方法操作。

           MVC的区分(分层概念)

           common目录下的模块都是M

           kemel目录下的模块是C

           test_Case.py, testRun.py等运行模块是V

            

3、正文

一、路径模块-initPath.py

       1、了解如何获取当前绝对路径:

             先了解python是如何取得当前目录的,我们可以使用内置库os来实现

             首先我们得知道 __file__在python中代表当前文件,

             然后在菜鸟教程里找到os.path的库,知道获取绝对路径的方法 os.path.abspath(path)

             然后开始在initPath.py写代码打印一下os.path.abspath(__file__)方法

             见下图,打印出了initPath.py的绝对路径。

        2、获取文件路径,不包含本身文件名

              在菜鸟找到了os.path,dirname()方法

              在initPath.py里使用打印一下这个方法,然后我们希望是\的路径,所以使用os.path.dirname(os.path.abspath(__file__))这个代码取路径

              然后我们再增加一层,os.path.dirname(os.path.dirname(os.path.abspath(__file__))),我们是要获取工程根目录路径的。如下图

        3、拼接所有路径

              我们要拼接目录,需要一个方法,进入菜鸟网,找到os.path.join(path1, path2),合成路径方法

             直接写initPath.py的代码了,将项目下所有文件夹的路径都定义好了。打印如下图

             initPath.py的代码段

  1. import os
  2. #get project dir
  3. BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  4. #get common dir
  5. COMMONDIR = os.path.join(BASEDIR, 'common')
  6. #get config dir
  7. CONFDIR = os.path.join(BASEDIR, 'config')
  8. #get data dir
  9. DATADIR = os.path.join(BASEDIR, 'data')
  10. #get library dir
  11. LIBDIR = os.path.join(BASEDIR, 'library')
  12. #get log dir
  13. LOGDIR = os.path.join(BASEDIR, 'log')
  14. #get report dir
  15. REPORTDIR = os.path.join(BASEDIR, 'report')
  16. #get testcaset dir
  17. CASEDIR = os.path.join(BASEDIR, 'testcases')
'
运行

二、配置文件模块-getConfig.py

        1、我们先了解一下配置文件

              在计算机领域,配置文件:是一种计算机文件,可以为一些计算机程序配置参数和初始设置。

              具体文件类型与相关知识点不多说,我们这里使用一ini配置文件,内容格式如下

             [about]

             aaa = bbb

             ccc = ddd

             其实about是节点,aaa = bbb是参数,key = value的意思

             所以在config/baseCon.ini下配置添加一些配置,如下图

         2、读取baseCon.ini里的配置项

              获取配置文件路径:

              读取配置文件之前,我们得先得到文件baseCon.ini的绝对路径,

              先引用已经定义好的config的绝对路径,然后使用os.path.join(path1, path2)方法将baseCon.ini的绝对路径生成,具体代码如下图

             了解configparser

              ConfigParser 是用来读取配置文件的包。三方库,所以需要通过命令 pip install configparser 来下载。

              在代码里直接导入ConfigParser类,然后创建其对像,调用方法read(),读取配置文件,具体代码如下图

               仔细学习上面的路子,下面就开始着手封装configparser了,这个库已经很完美了,不过我们后续读取配置文件时需要将读取文件这个步骤也省略,所以稍加封装。

               封装configparser配置文件读取

               封装的代码运行如下图

             

             nyCof.saveData()方法添加了配置项,写到了baseCon.ini里面

            getConfig.py的代码段:

  1. import os
  2. from common.initPath import CONFDIR
  3. from configparser import ConfigParser
  4. # conPath = os.path.join(CONFDIR, 'baseCon.ini')
  5. # print(conPath)
  6. # cnf = ConfigParser()
  7. # cnf.read(conPath, encoding='utf-8') #第一个参数是文件路径,第二个参数是读取的编码
  8. #
  9. # print('baseCon.ini里所有节点{}'.format(cnf.sections())) #打印所有节点名称
  10. # print('db下的所有key:{}'.format(cnf.options('db'))) #打印db节点下的所有key
  11. # print('db下的所有Item:{}'.format(cnf.items('db'))) #打印db节点下的所有item
  12. # print('db下的host的value:{}'.format(cnf.get('db', 'host'))) #打印某个节点的某个value
  13. """
  14. 定义Config继续ConfigParser
  15. """
  16. class Config(ConfigParser):
  17. def __init__(self):
  18. """
  19. 初始化
  20. 将配置文件读取出来
  21. super(). 调用父类
  22. """
  23. self.conf_name = os.path.join(CONFDIR, 'baseCon.ini')
  24. super().__init__()
  25. super().read(self.conf_name, encoding='utf-8')
  26. def getAllsections(self):
  27. """
  28. :return: 返回所有的节点名称
  29. """
  30. return super().sections()
  31. def getOptions(self, sectioName):
  32. """
  33. :param sectioName: 节点名称
  34. :return: 返回节点所有的key
  35. """
  36. return super().options(sectioName)
  37. def getItems(self, sectioName):
  38. """
  39. :param sectioName: 节点名称
  40. :return: 返回节点的所有item
  41. """
  42. return super().items(sectioName)
  43. def getValue(self, sectioName, key):
  44. """
  45. :param sectioName: 节点的名称
  46. :param key: key名称
  47. :return: 返回sectioName下key 的value
  48. """
  49. return super().get(sectioName, key)
  50. def saveData(self, sectioName, key, value):
  51. """
  52. 添加配置
  53. :param sectioName: 节点名称
  54. :param key: key名
  55. :param value: 值
  56. :return:
  57. """
  58. super().set(section=sectioName, option=key, value=value)
  59. super().write(fp=open(self.conf_name, 'w'))
  60. myCof = Config()
  61. #print(myCof.getAllsections())
  62. #print(myCof.getOptions('db'))
  63. #print(myCof.getItems('db'))
  64. #print(myCof.getValue('db', 'host'))
  65. #myCof.saveData('db', 'newKey', 'newValue')

三、读取用例模块-getCase.py

          读取用例是框架的比较重要,与独立的模块,不过读取用例前,我们先要设计用例文件与用户格式,一般我们可以将用例放在excel里,或者建立一个mysql数据库,将excel里的数据导入到里面,但后者比较麻烦,所以本文只接读取excel文件里的用例

         1、设计用例文件与用例格式

              在data目录里新增一个testcase.xlsx的excel文件,目录如下图:

              打开testcase.xlsx,在sheet1里设计用例表头,如下图:

             用例字段的含义:

              case_id:id,自己设定数字,只为了输出报告时识别用例的位置

              api:url里的接口路由,也是为了输出报告时识别用例的正确性

              title: 可以是用例的中文标题,也可以自定义方法中参数化接收数据的变量,格式可以是,abc或${abc}

              method: 请求类型 post/get ,或者自定义的方法名称

              url: 接口请求中的url,可以被参数化

              headers: 接口请求中的headers, 可以被参数化

              data:  接口请求中的data, 可以被参数化

              checkey:用例检查点的检查方法

              expected:  用例检查点的检查值

              Test_result:  用例执行完成后,回写给用例的状态,pass/fail,不过我一般不用。

             用例示例如下图:

          

         2、设计用例集JSON格式 

               格式如下:

  1.             [ #用例集
  2.                   { #第一条用例
  3.                      key1: value1, #用例里的字段与字段值
  4.                      key2: value2
  5.                      ...
  6.                    },
  7.                    { #第二条用例
  8.                        key1:value1, #用例里的字段与字段
  9.                        key2: value2
  10.                        ...
  11.                     },
  12.                     ....
  13.               ]

                    根据用例excel文件,具体的JSON用例串应该下面是这样子的,前面字段是excel列表数据,最后一个是sheet_name是表示了sheet页名称。一般接口用例按模块编写,一个模块一个sheet页,以此字段区分,为后面输出测试报告做准备。

[

      {

            'case_id':1,

            'api': 'publickey',

             'title':'url',

             'method':'设置变量',

             'url':'https://www.adbcde.com.cn/',

             'headers':'Host:www.adbcde.com.cn',

             'data':'userToken=16029513602050&loginpassword=qwert12345&loginphone=15361828291',

             'checkey':'结果包含',

             'expected':'"return_code": "SUCCESS"',

             'Test result':None,

             'sheet_name':'sheet1',

       },

       {

          ......

       }

.....

]

         3、openpyxl库学习

               读写excel的库有很多,因为用例要求不高,所以选择openpyxl库来封装用例代码

               安装使用命令 pip install openpyxl

               开始写代码

               操作excel用例之前,要得到文件的绝对路径

               获取绝对路径成功,接下来开始读取excel里的文件了,

               读取excel文件内容

                步聚如下图,1、先打开excel文件,2、遍历一下sheet页输出名称,3遍历输出Sheet1里的数据,这三个写后,就表名,excel可以读取成功了。openpyxl也学完了

             4、封装用例类

                  我定义的用例是一个excel文件一个项目,在工作中,应该不止一个项目,所以有可能在data目录里有多个excel文件,我们需要执行哪个呢,所以此能用到前面写的配置文件,在里面加一个case节点,增加执行用例文件名称

                  一个用例文件中肯定有很多注释的用例,所以定义一个 # 来区分注释用例。两个定义如下图

                      封装代码无法载图全部,先看执行结果,后面读取用例只需要两行代码就可将用例JSON读取出来

                     getCase.py的代码段

  1. import os
  2. import openpyxl
  3. from common.initPath import DATADIR
  4. from common.getConfig import myCof
  5. # #拼接用例文件绝对路径
  6. # caseFile = os.path.join(DATADIR, 'testcase.xlsx')
  7. # print(caseFile)
  8. # #读取excel文件
  9. # xl = openpyxl.open(filename=caseFile)
  10. # #打印caseFile里的sheet页名称
  11. # print('打印所有sheet页')
  12. # for sheet in xl:
  13. # print(sheet.title)
  14. # #打印excel里的所有的行字段数据
  15. # print('打印Sheet1里的所有的行字段数据')
  16. # sh = xl['Sheet1']
  17. # data = list(sh.rows)
  18. # for da in data:
  19. # for k in da:
  20. # print(k.value)
  21. class Getcase(object):
  22. def __init__(self, sheet_name=None):
  23. """
  24. 初始化文件名称,sheet名称,excel对像
  25. :param sheet_name: 传入的sheet名称 ,可以为空。
  26. """
  27. filename = myCof.getValue('case', 'testCase')
  28. self.note = myCof.getValue('identifier', 'note')
  29. self.caseFile = os.path.join(DATADIR, filename)
  30. self.sheet_name = sheet_name
  31. self.wb = None
  32. def openexcel(self):
  33. """
  34. 打开excel文件
  35. 如果sheet名称不为空,定位到对应sheet页
  36. :return:
  37. """
  38. self.wb = openpyxl.open(self.caseFile)
  39. if self.sheet_name is not None:
  40. self.sh = self.wb[self.sheet_name]
  41. def read_excels(self):
  42. """
  43. 格式化用例集
  44. 用例格式JSON见上面的前面的描述
  45. 过滤掉#注释的用例
  46. :return:
  47. """
  48. if self.wb is None:
  49. self.openexcel()
  50. datas = list(self.sh.rows)
  51. title = [i.value for i in datas[0]]
  52. cases = []
  53. for i in datas[1:]:
  54. data = [k.value for k in i]
  55. case = dict(zip(title, data)) #将数据格式化中JSON串
  56. try:
  57. if str(case['case_id'])[0] is not self.note: # 过滤掉note符号开头的用例,注释掉不收集、不执行
  58. case['sheet'] = self.sh.title
  59. cases.append(case)
  60. except KeyError:
  61. cases.append(case)
  62. return cases
  63. def read_all_excels(self):
  64. """
  65. 遍历所有的sheet页
  66. 取得所有用例集,再格式下一次,
  67. 过滤掉#注释的sheet页
  68. :return:
  69. """
  70. self.openexcel()
  71. cases = []
  72. for sheet in self.wb:
  73. if sheet.title[0] is not self.note: # 过滤掉note符号开头的sheet页,注释掉的不收集,不执行
  74. self.sh = sheet
  75. cases += self.read_excels()
  76. return cases
  77. def write_excels(self, rows, column, value):
  78. """
  79. 回写用例字段
  80. :param rows:
  81. :param column:
  82. :param value:
  83. :return:
  84. """
  85. self.openexcel()
  86. self.sh.cell(row=rows, column=column, value=value)
  87. self.wb.save(self.caseFile)
  88. # readExce = Getcase()
  89. # print(readExce.read_all_excels())

四、数据库操作模块-operatorDB.py

         1、pymysql安装

               安装pymysql使用命令pip install pymysql

         2、pymysql学习

               学习之前,我们先把连接数据库的相关配置参数,在配置文件中取出来

               如下图,先导入myCof对像,使用getValue取出对应该的配置参数打印成功

               pymysql已经导入成功了,连接数据参数也取出来了,接下来开始连接数据,执行SQL语句

               连接数据库

               如果没连接成功会抛出异常,所以此时需要 try ... except.....来catch异常,打印出来,下图为连接数据库超时,因为还没起Mysql的服务。

                这里我们得先配置好一个mysql服务器,方便调试,不懂的可以在网上找教程学习学习,当然直接用公司的测试环境也行,省时少力。

               打开mysql服务后,运行,会报一个错,我们将port参数强转化为int类型,他不能为str类型。

               强转后,再运行一次,连接成功了

               执行SQL

               执行SQL语句,我们需要游标,先获取游标,再使用游标执行SQL,一次通过

               打印SQL查询的数据

               pymysql学习完成,接下来可以封装了

         3、封装数据操作

               封装的逻辑是,将连接作一个方法,执行SQL写一个方法,关闭连接写一个方法

               先看封装好后的执行结果,只需要写四行代码就可以执行SQL语句了。

                opeartorDB.py的代码段:

  1. import pymysql
  2. from common.getConfig import myCof
  3. # host = myCof.getValue('db', 'host')
  4. # port = int(myCof.getValue('db', 'port'))
  5. # user = myCof.getValue('db', 'user')
  6. # pwd = myCof.getValue('db', 'pwd')
  7. # database = myCof.getValue('db', 'database')
  8. # charset = myCof.getValue('db', 'charset')
  9. # try:
  10. # #连接数据库
  11. # db = pymysql.connect(host=host, port=port, user=user, password=pwd, database=database, charset=charset)
  12. # #获取游标
  13. # cursor = db.cursor()
  14. # #执行SQL
  15. # cursor.execute("select * from Student where SName = '林小六';")
  16. # #获取查询结果
  17. # result = cursor.fetchall()
  18. # #打印查询结果
  19. # print(result)
  20. # print('执行成功')
  21. # except Exception as e:
  22. # print('连接失败,原因:{}'.format(str(e)))
  23. """
  24. 封装mysql操作
  25. """
  26. class OpeartorDB(object):
  27. def __init__(self):
  28. """
  29. 初始化方法,习惯性留着
  30. """
  31. pass
  32. def connectDB(self):
  33. """
  34. 连接数据库
  35. :return: 返回成功失败,原因
  36. """
  37. host = myCof.getValue('db', 'host')
  38. port = myCof.getValue('db', 'port')
  39. user = myCof.getValue('db', 'user')
  40. pwd = myCof.getValue('db', 'pwd')
  41. database = myCof.getValue('db', 'database')
  42. charset = myCof.getValue('db', 'charset')
  43. try:
  44. self.db = pymysql.connect(host=host, port=int(port), user=user, password=pwd, database=database, charset=charset)
  45. return True, '连接数据成功'
  46. except Exception as e:
  47. return False, '连接数据失败【' + str(e) + '】'
  48. def closeDB(self):
  49. """
  50. 关闭数据连接,不关闭会导致数据连接数不能释放,影响数据库性能
  51. :return:
  52. """
  53. self.db.close()
  54. def excetSql(self, enpsql):
  55. """
  56. 执行sql方法,
  57. :param enpsql: 传入的sql语句
  58. :return: 返回成功与执行结果 或 失败与失败原因
  59. """
  60. isOK, result = self.connectDB()
  61. if isOK is False:
  62. return isOK, result
  63. try:
  64. cursor = self.db.cursor()
  65. cursor.execute(enpsql)
  66. res = cursor.fetchone() #为了自动化测试的速度,一般场景所以只取一条数据
  67. if res is not None and 'select' in enpsql.lower(): #判断是不是查询,
  68. des = cursor.description[0]
  69. result = dict(zip(des, res)) #将返回数据格式化成JSON串
  70. elif res is None and ('insert' in enpsql.lower() or 'update' in enpsql.lower()): #判断是不是插入或者更新数据
  71. self.db.commit() #提交数据操作,不然插入或者更新,数据只会更新在缓存,没正式落库
  72. result = '' #操作数据,不需要返回数据
  73. cursor.close() #关闭游标
  74. self.closeDB() #关闭数据连接
  75. return True, result
  76. except Exception as e:
  77. return False, 'SQL执行失败,原因:[' + str(e) + ']'
  78. # sql = 'select * from Student'
  79. # oper = OpeartorDB()
  80. # isOK, result = oper.excetSql(sql)
  81. # print(result)

五、日志模块-log.py

         1、logging学习

               logging是python的基础库,不需要下载,直接导入可用

               日志有五个等级,自动测试一般INFO等都打印,所以我们在配置文件里的加上日志参数配置

                [log]

                level = INFO

               打印日志

               编写代码,先获取日志等级配置,然后设置日志等级,初始化日志对像,打印日志,因为日志等是INFO,所以debug的日志不会打印,代码如下图

               设置日志格式

                格式设置如下图

 

                将日志输出到文件

                日志文件存放在log目录下,所以先获取导入目录与os

                设计日志文件隔一天时间,日志就另新增一个,保留十五天,所以需要导入logging里的一个方法TimedRotatingFileHandler、

from logging.handlers import TimedRotatingFileHandler  #导入的方法

                 代码如下图

                运行后,输出了testReport文件,里面打印了执行日志

                logging基本学习完成 ,再简单封装一下

         2、日志模块封装

               封装日志这一块,不需要创建对像,因为他本身需要返回一个logging的对象,对象操作对象,别扭,所以在Log类里直接封装一个静态的方法,可以直接类调用方法返回一个logging对象。

               调式执行结果与上面一致,但不截图了,直接上代码

               log.py代码段

  1. import os
  2. import logging
  3. from common.getConfig import myCof
  4. from common.initPath import LOGDIR
  5. from logging.handlers import TimedRotatingFileHandler
  6. # # 获取日志等配置参数
  7. # level = myCof.getValue('log', 'level')
  8. # # 设置日志格式,%(asctime)s表示时间,%(name)s表示传入的标识名,%(levelname)s表示日志等级,%(message)s表示日志消息
  9. # format = '%(asctime)s - %(name)s-%(levelname)s: %(message)s'
  10. # # 设置日志基础等级, 设置
  11. # logging.basicConfig(level=level, format=format)
  12. # # 初始化日志对像,Hunwei是name
  13. # mylog = logging.getLogger('Hunwei')
  14. # #拼接日志目录
  15. # log_path = os.path.join(LOGDIR, 'testReport')
  16. # #生成文件句柄,filename是文件路径,when表是时间D表示天,backuCount=15目录下最多15个日志文件,enccoding='utf-8'日志字符格式
  17. # fh = TimedRotatingFileHandler(filename=log_path, when="D", backupCount=15, encoding='utf-8')
  18. # #设置历史日志文件名称的格式,会自动按照某天生成对应的日志
  19. # fh.suffix = "%Y-%m-%d.log"
  20. # #设置文件输出的日志等级
  21. # fh.setLevel(level)
  22. # #设置文件输出的日志格式
  23. # fh.setFormatter(logging.Formatter("%(asctime)s - %(name)s-%(levelname)s: %(message)s"))
  24. # #将文件句柄加入日志对象
  25. # mylog.addHandler(fh)
  26. #
  27. # mylog.debug('debug')
  28. # mylog.info('info')
  29. # mylog.warn('warm')
  30. # mylog.error('error')
  31. # mylog.fatal('fatal')
  32. class Log(object):
  33. @staticmethod
  34. def getMylog():
  35. # 获取日志等配置参数
  36. level = myCof.getValue('log', 'level')
  37. # 设置日志格式,%(asctime)s表示时间,%(name)s表示传入的标识名,%(levelname)s表示日志等级,%(message)s表示日志消息
  38. format = '%(asctime)s - %(name)s-%(levelname)s: %(message)s'
  39. # 设置日志基础等级, 设置
  40. logging.basicConfig(level=level, format=format)
  41. # 初始化日志对像,Hunwei是name
  42. mylog = logging.getLogger('Hunwei')
  43. # 拼接日志目录
  44. log_path = os.path.join(LOGDIR, 'testReport')
  45. # 生成文件句柄,filename是文件路径,when表是时间D表示天,backuCount=15目录下最多15个日志文件,enccoding='utf-8'日志字符格式
  46. fh = TimedRotatingFileHandler(filename=log_path, when="D", backupCount=15, encoding='utf-8')
  47. # 设置历史日志文件名称的格式,会自动按照某天生成对应的日志
  48. fh.suffix = "%Y-%m-%d.log"
  49. # 设置文件输出的日志等级
  50. fh.setLevel(level)
  51. # 设置文件输出的日志格式
  52. fh.setFormatter(logging.Formatter("%(asctime)s - %(name)s-%(levelname)s: %(message)s"))
  53. # 将文件句柄加入日志对象
  54. mylog.addHandler(fh)
  55. #返回logging对像
  56. return mylog
  57. #调式代码
  58. mylog = Log.getMylog()
  59. # mylog.debug('debug')
  60. # mylog.info('info')
  61. # mylog.warn('warm')
  62. # mylog.error('error')
  63. # mylog.fatal('fatal')

六、邮件模块-sendEmail.py

          1、开启邮箱的SMTP服务

                email邮箱提供商都可以开启smtp服务的,如果知道什么是smtp并知道设置的朋友可以略过这一段

               以qq邮箱为例

               进入qqmail.com登录邮箱,找到【设置】-【账户】,点击POP3/SMTP 开启(下图标记的有误,别被误导了哈)

               按描述发送短信

               开启之后,我们会得到一个密钥,好好保存,

          2、学习使用smtplib库发送带附件的邮件

               邮件参数添加到配置文件

               host 是邮件服务器,腾讯的是smtp.qq.com,  

               port 是邮件服务器端口,开通smtp时,腾讯会邮件告之端口号

               user是邮箱、pwd是开通smtp时得到的密钥

               from_addr 是发送邮箱地址,与user是同一个

               to_addr是收件箱,可以做成逗号分隔

                   连接smtp服务器

                   一般连接啥服务器没成功都会抛异常呀,所以用一下try,新建一个smtplib的对像,带上服务器与端口,然后使用用户名密码连接  

                    发送邮件

                    发送邮件之前需要构建一个邮件内容,所以所以email库,可以通过pip install email下载,使用

                    先构建一个纯文本的内容 ,所以导入 MIMEText,

                    下面是构建邮件内容与发送成功的截图,msg消息休是邮件内容 ,里面需要文本,发送人,收件人,邮件主题等参数

                    发送成功后,进入邮箱查看邮件

               邮件发送成功后了,基础的已经学会了,还有两种邮件类型、MIMEMultipart多媒体内型,MIMEApplication附件内型,不多赘述,看后面封装代码即可通明

               

          3、封装代码

                封装代码前呢,先知道了一般的自动化报告是html格式的,这里我拿了以前的测试放在工程report目录下,方便使用,如下图

                封装邮件模块后,两行代码即可发送测试报告。

           

                sendEmail.py的代码段

  1. import os
  2. import smtplib
  3. from common.getConfig import myCof
  4. from email.mime.text import MIMEText #导入纯文本格式
  5. from email.mime.multipart import MIMEMultipart
  6. from email.mime.application import MIMEApplication
  7. from common.initPath import REPORTDIR
  8. # host = myCof.getValue('email', 'host')
  9. # port = int(myCof.getValue('email', 'port'))
  10. # user = myCof.getValue('email', 'user')
  11. # pwd = myCof.getValue('email', 'pwd')
  12. # to_addr = myCof.getValue('email', 'to_addr')
  13. # #定义纯文本消息 ,From定义发件人, To定义收件人, Subject定义邮件标题
  14. # msg = MIMEText('hello,send by python_test...','plain','utf-8')
  15. # msg['From'] = user
  16. # msg['To'] = to_addr
  17. # msg['Subject'] = '测试邮件发送'
  18. # try:
  19. # #连接smtp服务对话,创建对像
  20. # smtp = smtplib.SMTP_SSL(host=host, port=port)
  21. # #登录服务器
  22. # smtp.login(user=user, password=pwd)
  23. # # 发送邮件
  24. # smtp.sendmail(from_addr=user, to_addrs=to_addr, msg=msg.as_string())
  25. # # 结束与服务器的对话
  26. # smtp.quit()
  27. # print('发送邮件成功')
  28. # except Exception as e:
  29. # print('发送邮件失败,原因:{}'.format(str(e)))
  30. class SendMail(object):
  31. def __init__(self):
  32. """
  33. 初始化文件路径与相关配置
  34. """
  35. all_path = []
  36. #获取测试报告目录下的报告文件名称
  37. for maindir, subdir, file_list in os.walk(REPORTDIR):
  38. pass
  39. #拼接文件绝对路径
  40. for filename in file_list:
  41. all_path.append(os.path.join(REPORTDIR, filename))
  42. self.filename = all_path[0]
  43. self.host = myCof.get('email', 'host')
  44. self.port = myCof.get('email', 'port')
  45. self.user = myCof.get('email', 'user')
  46. self.pwd = myCof.get('email', 'pwd')
  47. self.from_addr = myCof.get('email', 'from_addr')
  48. self.to_addr = myCof.get('email', 'to_addr')
  49. def get_email_host_smtp(self):
  50. """
  51. 连接stmp服务器
  52. :return:
  53. """
  54. try:
  55. self.smtp = smtplib.SMTP_SSL(host=self.host, port=self.port)
  56. self.smtp.login(user=self.user, password=self.pwd)
  57. return True, '连接成功'
  58. except Exception as e:
  59. return False, '连接邮箱服务器失败,原因:' + str(e)
  60. def made_msg(self):
  61. """
  62. 构建一封邮件
  63. :return:
  64. """
  65. # 新增一个多组件邮件
  66. self.msg = MIMEMultipart()
  67. with open(self.filename, 'rb') as f:
  68. content = f.read()
  69. # 创建文本内容
  70. text_msg = MIMEText(content, _subtype='html', _charset='utf8')
  71. # 添加到多组件的邮件中
  72. self.msg.attach(text_msg)
  73. # 创建邮件的附件
  74. report_file = MIMEApplication(content)
  75. report_file.add_header('Content-Disposition', 'attachment', filename=str.split(self.filename, '\\').pop())
  76. self.msg.attach(report_file)
  77. # 主题
  78. self.msg['subject'] = '自动化测试报告'
  79. # 发件人
  80. self.msg['From'] = self.from_addr
  81. # 收件人
  82. self.msg['To'] = self.to_addr
  83. def send_email(self):
  84. """
  85. 发送邮件
  86. :return:
  87. """
  88. isOK, result = self.get_email_host_smtp()
  89. if isOK:
  90. self.made_msg()
  91. self.smtp.send_message(self.msg, from_addr=self.from_addr, to_addrs=self.to_addr)
  92. else:
  93. return isOK, result
  94. # abc = SendMail()
  95. # abc.send_email()

七、消息模块-sendMsg.py

          1、创建企业微信应用           

               在企业微信建立一个应用,为接收消息的载体,添加相关人员

               在Python中实现得到企业微信应用token,

               在企业微信官网创建一个公司,或者用公司的企业微信号,获取企业微信的企业ID

              创建应用

               得到AgentId、Secret

               进入新建的应用详情页面,可以得到这两个字段

       1、学习发送企业微信消息

             资深开发者直接会找企业微信的API开发指南,应该知道怎么封装了。

              先将创建应该时得到的三个参数配置到baseCon.ini文件里

                 

             获取企业微信的token

             拼接得获取token的API url,corpid与corpsecret字段为参数,用requests.get()方法请求,从结果集中解析出token字段值

                发送消息

               发送消息的代码如下图,先拼接发送消息的api的url,需要添加上面得到token值,再构造一个消息,转换成bytes格式的消息休,使用requests.post发送消息

企业微信收到了消息,如下图

       2、代码封装

            封装完成后,只需要两行代码就可以发送消息了

           sendMsg.py的代码段

  1. import requests
  2. import json
  3. from common.getConfig import myCof
  4. # # 获取企业微信的参数
  5. # corpid = myCof.get('wechat', 'corpid')
  6. # corpsecret = myCof.get('wechat', 'corpsecret')
  7. # agentid = myCof.get('wechat', 'agentid')
  8. # # 拼接获取token的API
  9. # url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' + corpid + '&corpsecret=' + corpsecret
  10. # # 使用requests请求API,转为JSON格式
  11. # response = requests.get(url)
  12. # res = response.json()
  13. # #获取token打印
  14. # token = res['access_token']
  15. # print(token)
  16. # # 拼接发送消息的api
  17. # url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + token
  18. # # 构建一个消息JSON串
  19. # jsonmsg = {
  20. # "touser" : "@all",
  21. # "msgtype" : "text",
  22. # "agentid" : agentid,
  23. # "text" : {
  24. # "content" : "API接口从无到有"
  25. # },
  26. # "safe":0
  27. # }
  28. # # 将JSON转成str,再转成bytes格式的消息休
  29. # data = (bytes(json.dumps(jsonmsg), 'utf-8'))
  30. # # 使用requests post发送消息
  31. # requests.post(url, data, verify=False)
  32. class SendMsg(object):
  33. def __init__(self):
  34. self.corpid = myCof.get('wechat', 'corpid')
  35. self.corpsecret = myCof.get('wechat', 'corpsecret')
  36. self.agentid = myCof.get('wechat', 'agentid')
  37. def getToken(self):
  38. if self.corpid is None or self.corpsecret is None:
  39. return False, '企业微信相关信息未配置'
  40. url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' + self.corpid + '&corpsecret=' + self.corpsecret
  41. response = requests.get(url)
  42. res = response.json()
  43. self.token = res['access_token']
  44. return True, '企业微信token获取成功'
  45. def sendMsg(self, msg):
  46. _isOK, result = self.getToken()
  47. if _isOK:
  48. url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + self.token
  49. jsonmsg = {
  50. "touser" : "@all",
  51. "msgtype" : "text",
  52. "agentid" : self.agentid,
  53. "text" : {
  54. "content" : msg
  55. },
  56. "safe":0
  57. }
  58. data = (bytes(json.dumps(jsonmsg), 'utf-8'))
  59. requests.post(url, data, verify=False)
  60. else:
  61. print(result)
  62. # wechatMsg = SendMsg()
  63. # wechatMsg.sendMsg('API接口从无到有')

八、变量参数化模块-parameteriZation.py

                前面七小节已经将自动化测试框架的周边全部弄完了,接下来便开始写核心模块了。

                自动化测试工具有很多变量可以配置,分为两大类,系统环境变量(在执行之前,人为配置好的变量,执行完还存在),临时变量(在执行过程参数化的变量,执行完成消失,等待下次参数化)

          1、系统环境变量

                这个很简单 ,我们直接写在配置文件即可,比如我们需要一个账号与密码,将其设置为系统环境变量,直接在bascCon.ini里添加

                

                然后我们导入 myCon,使用getValue方法就可以取到变量的参数了,

                

               这还是第一步,我们需要设计变量的格式,比如 {aaa}, ${aaa},前者使用{}来标识aaa需要参数化,后者用${}来标识aaa需要参数化,

               那就将${}定义为变量的标识符

               取变量名

               那么我们将如何来取变量名呢,比如,${phone},直接使用是取不到手机号码的,需要将phone取出来。此时我们可以使用正则表达式库,re来处理

               正则表达式很强大,规则也很多,在这里不做赘述。re的话是Python的基础库,所以可以在菜鸟站上搜索到教程,我们要用到的方法就这两个

     

      

               代码如下与执行结果见下图

               

               将变量参数化   

               就是在上面代码里加上获取配置参数即可,代码与执行结果见下图:变量已经参数化了。

           2、临时变量

                 临时变量,这是个检验Python代码基础的活儿,面向对象,属性,setattr  ,getattr等知识点。如果懂了我们只需要在上加几行代码就成了。

                先定义一个空类。不给属性与方法,在执行代码的过程中使用setattr给这个空类添加属性与值,这个属性即 临时变量,如果想调用,即可用getattr取属性的值,进行参数化

               代码如下与执行结果如下

              

    

              临时变量参数化

           设置好临时变量,参数化过程与系统环境变量的差不多,区别是将myCon.getValue(‘par’,  key)  改成getattr(Paramte, key)

           代码与执行结果如下图:

          3、代码封装

                parameteriZation.py的代码段

  1. import re
  2. from common.getConfig import myCof
  3. # phone = myCof.getValue('par','phone')
  4. # print(phone)
  5. # pwd = myCof.getValue('par', 'pwd')
  6. # print(pwd)
  7. #定义一个字符串,里面有两个变量
  8. # data = '{PHONE : ${phone}, PASSWORD: ${pwd}}'
  9. # #定义正则匹配规则
  10. # ru = r'\${(.*?)}'
  11. # #循环取变量名称
  12. # while re.search(ru, data):
  13. # #取值第一个变量
  14. # res = re.search(ru, data)
  15. # #取出名称
  16. # key = res.group(1)
  17. # #取出环境变量
  18. # value = myCof.getValue('par', key)
  19. # #替换变量
  20. # data = re.sub(ru, value, data, 1)
  21. # #打印替换后的字符串
  22. # print(data)
  23. # 给临时变量的空类
  24. # class Paramete():
  25. # pass
  26. # # 设置临时变量
  27. # setattr(Paramete, 'phone', '15381819299')
  28. # setattr(Paramete, 'pwd', '654321')
  29. # # 直接调用取值打印
  30. # # print('直接打印:' + Paramete().phone)
  31. # # 通过getattr打印
  32. # # print('getattr打印:' + getattr(Paramete, 'phone'))
  33. # data = '{PHONE : ${phone}, PASSWORD: ${pwd}}'
  34. # #定义正则匹配规则
  35. # ru = r'\${(.*?)}'
  36. # #循环取变量名称
  37. # while re.search(ru, data):
  38. # #取值第一个变量
  39. # res = re.search(ru, data)
  40. # #取出名称
  41. # key = res.group(1)
  42. # #取出环境变量
  43. # value = getattr(Paramete, key)
  44. # #替换变量
  45. # data = re.sub(ru, value, data, 1)
  46. #
  47. # print(data)
  48. class Paramete:
  49. pass
  50. def replace_data(data):
  51. """
  52. 替换变量
  53. :param data:
  54. :return:
  55. """
  56. ru = r'\${(.*?)}'
  57. while re.search(ru, data):
  58. res = re.search(ru, data)
  59. item = res.group()
  60. keys = res.group(1)
  61. # 先找系统环境变量,如果有则替换;如果没有则找临时变量
  62. try:
  63. value = myCof.get('test_data', keys)
  64. except Exception as e:
  65. value = getattr(Paramete, keys).encode('utf-8').decode('unicode_escape')
  66. finally:
  67. data = re.sub(ru, value, data, 1)
  68. return data
  69. def analyzing_param(param):
  70. """
  71. ${abc}取出abc
  72. :param param:
  73. :return:
  74. """
  75. ru = r'\${(.*?)}'
  76. if re.search(ru, param):
  77. return re.findall(ru, param)[0]
  78. return param
  79. # print(replace_data('${phone}, ${pwd}'))

九、API请求模块-sendApirequests.py

       1、requests库下载

            第三方库,所以需要用命令:pip install requests   下载

       2、requests库学习

            requests的请求类型

            常用的请求类型都在下图

            

            目前主要的请求是get与post

             requests.get(url=‘请求api的url’, params=‘get请求的参数,可以为空’, headers=‘请求头,如果接口没有校验,可以为空’)

             requests.post(url=‘请求api的url’, json=‘如果json参数,使用json字段’, data=‘如果是表单格式,使用data参数’, files=‘当数据为文件时,使用file参数’, headers=‘请求头,如果接口没有校验,可以为空’)

              post里的可以传json、data、file三种参数,但三个只能传一个。

       3、api请求封装

            sendApirequest.py代码段

            这个文件在kemel目录下面

  1. class SendApirequests(object):
  2. def __init__(self):
  3. self.session = requests.session()
  4. def request_Obj(self, method, url, params=None, data=None, json=None, files=None, headers=None,):
  5. opetype = str.lower(method)
  6. if opetype == 'get':
  7. response = requests.get(url=url, params=params, headers=headers)
  8. elif opetype == 'post':
  9. response = requests.post(url=url, json=json, data=data, files=files, headers=headers)
  10. return response
'
运行

             封装代码调用-get            

 

             封装代码调用-post  

十、公共方法的模块(核心)-commKeyword.py

      1、公共方法理念

            一个软件MCV的层次概念

            M是底层,模型层,前面封装的代码都是这种模块,属于底层代码 。

            C是中层,控制层,封装所有底层的接入方法。

            V是高层,会话层,界面也操作提供给用户使用。

           MVC的区分(分层概念)

           common目录下的模块都是M

           kemel目录下的模块是C

           解析用例与运行文件是V

           公共方法、主要是分装所有底层模块的操作入口,并提供一些特殊性的公共方法

            什么是特殊的公共方法呢?

             比如:自动生成手机号码、自动生成身份证号码,自动生成随机字符串,自动生成全国各地区号,设置变量、拆分字段,获取字符串中的指定字段等等。

      2、封装

            封装完成公共方法,会与后面的工厂结合起来使用

           commKeyword.py的代码段

            

  1. import json
  2. import jsonpath
  3. import datetime
  4. from common.getConfig import myCof
  5. from common.getCase import Getcase
  6. from common.operatorDB import OpeartorDB
  7. from common.parameteriZation import Paramete, analyzing_param, replace_data
  8. from common.sendApirequest import SendApirequests
  9. from common.sendEmail import SendMail
  10. from common.sendMsg import SendMsg
  11. class CommKeyword(object):
  12. def __init__(self):
  13. self.operatordb = OpeartorDB()
  14. self.getCase = Getcase()
  15. self.sendApi = SendApirequests()
  16. self.sendMail = SendMail()
  17. self.sedMsg = SendMsg()
  18. def get_exceut_case(self, **kwargs):
  19. """
  20. 获取当前执行用例
  21. :return: bl, cases 一参数返回成功与否,二参数用例或失败原因
  22. """
  23. try:
  24. cases = self.getCase.read_all_excels()
  25. except Exception as e:
  26. return False, '获取用例失败,原因:' + str(e)
  27. return True, cases
  28. def get_current_casefile_name(self, **kwargs):
  29. """
  30. 获取执行用例文件名称
  31. :return: 返回用例文件名称
  32. """
  33. try:
  34. fileName = myCof.getValue('case', 'testcase')
  35. except Exception as e:
  36. return False, '参数中未设置用例文件名称,请检查配置文件'
  37. return True, fileName
  38. def send_api(self, **kwargs):
  39. """
  40. 发送用例请求 post, get
  41. :param kwargs: 请求的参数 ,有url,headers,data等
  42. :return: bl, cases 一参数返回成功与否,二参数请求结果或失败原因
  43. """
  44. try:
  45. url = replace_data(kwargs['url'])
  46. method = kwargs['method']
  47. if kwargs['headers'] is None:
  48. headers = None
  49. else:
  50. _isOk, result = self.format_headers(replace_data(kwargs['headers']))
  51. if _isOk:
  52. headers = result
  53. else:
  54. return _isOk, result
  55. if kwargs['data'] is not None:
  56. try:
  57. jsondata = json.loads(replace_data(kwargs['data']))
  58. data = None
  59. except ValueError:
  60. data = replace_data(kwargs['data'])
  61. jsondata = None
  62. else:
  63. data = None
  64. jsondata = None
  65. response = self.sendApi.request_Obj(method=method, url=url, json=jsondata, data=data, headers=headers)
  66. except Exception as e:
  67. return False, '发送请求失败' + str(e)
  68. return True, response
  69. def set_sheet_dict(self):
  70. """
  71. :return: excl文件里面的sheet页信息
  72. """
  73. xlsx = Getcase(myCof.get('excel', 'casename'))
  74. sh_dict = xlsx.sheet_count()
  75. setattr(Paramete, 'sheetdict', sh_dict)
  76. sheetdict = getattr(Paramete, 'sheetdict')
  77. return sheetdict
  78. def set_common_param(self, key, value):
  79. """
  80. :param key: 公共变量名
  81. :param value: 参数
  82. :return:
  83. """
  84. setattr(Paramete, key, value)
  85. def get_commom_param(self, key):
  86. """
  87. :param key: 公共变量名
  88. :return: 取变量值
  89. """
  90. return getattr(Paramete, key)
  91. def get_current_sheet_name(self):
  92. """
  93. :return: 返回当前执行用例的sheet页名称
  94. """
  95. sh_index = self.get_commom_param('sheetindex')
  96. sh_dict = self.get_commom_param('sheetdict')
  97. for sh in sh_dict:
  98. if sh.title().find(str(sh_index)) != -1:
  99. sheet_name = sh_dict[sh.title().lower()]
  100. return sheet_name
  101. def get_json_value_as_key(self, *args, **kwargs):
  102. """
  103. 得到json中key对应的value,存变量param
  104. 默认传的参数为:
  105. result:用来接收结果的变量
  106. method:调用的方法 ,带不带${ } 都行
  107. param_x:参数,数量不限。格式可为${ }会替换为已存在的数据
  108. """
  109. try:
  110. param = kwargs['result']
  111. jsonstr = kwargs['param_1']
  112. key = kwargs['param_2']
  113. except KeyError:
  114. return False, '方法缺少参数,执行失败'
  115. param = analyzing_param(param)
  116. jsonstr = replace_data(jsonstr)
  117. key = replace_data(key)
  118. if param is None or jsonstr is None or key is None:
  119. return False, '传入的参数为空,执行失败'
  120. try:
  121. result = json.loads(jsonstr)
  122. except Exception:
  123. return False, '传入字典参数格式错误,执行失败'
  124. key = '$..' + key
  125. try:
  126. value = str(jsonpath.jsonpath(result, key)[0])
  127. except Exception:
  128. return False, '字典中[' + jsonstr + ']没有键[' + key + '], 执行失败'
  129. setattr(Paramete, param, value)
  130. return True, ' 已经取得[' + value + ']==>[${' + param + '}]'
  131. def format_headers(self, param):
  132. """
  133. 格式化请求头
  134. :param param:excel里读出出来的header,是从浏览器f12里直接copy的
  135. :return:
  136. """
  137. if param is None:
  138. return False, 'Headers为空'
  139. list_header = param.split('\n')
  140. headers = {}
  141. for li in list_header:
  142. buff = li.split(':')
  143. try:
  144. headers[buff[0]] = buff[1]
  145. except IndexError:
  146. return False, 'Headers格式不对'
  147. return True, headers
  148. def set_variable(self, **kwargs):
  149. """
  150. 设置变量
  151. :param kwargs:
  152. :return:
  153. """
  154. try:
  155. var = kwargs['result']
  156. param = kwargs['param_1']
  157. except KeyError:
  158. return False, '方法缺少参数,执行失败'
  159. if var is None or param is None:
  160. return False, '传入的参数为空,执行失败'
  161. setattr(Paramete, var, param)
  162. return True, ' 已经设置变量[' + param + ']==>[${' + var + '}]'
  163. def execut_sql(self, **kwargs):
  164. """
  165. 执行SQL
  166. :param kwargs:
  167. :return:
  168. """
  169. try:
  170. sql = kwargs['param_1']
  171. except KeyError:
  172. return False, '方法缺少参数,执行失败'
  173. try:
  174. var = kwargs['result']
  175. par = kwargs['param_2']
  176. except Exception:
  177. var = None
  178. isOK, result = self.operatordb.excetSql(sql)
  179. if isOK and var is not None:
  180. data = result[par]
  181. setattr(Paramete, var, data)
  182. return True, '执行SQL:[' + sql + ']成功,取得' + par + '的数据[' + data + ']==>[${' + var + '}]'
  183. elif isOK and var is None:
  184. return True, '执行SQL:[' + sql + ']成功'
  185. elif isOK is False:
  186. return isOK, result
  187. def send_email(self):
  188. """
  189. 发送邮件
  190. :return:
  191. """
  192. return self.sendMail.send_email()
  193. def send_msg(self, **kwargs):
  194. """
  195. 发送消息
  196. :param kwargs:
  197. :return:
  198. """
  199. title = kwargs['title']
  200. url = kwargs['url']
  201. code = kwargs['code']
  202. result = kwargs['result'].encode('utf-8').decode('unicode_escape')
  203. nowTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 现在
  204. msg = nowTime + '\n用例名称:' + title + '\n请求:' + url + '\n响应码:' + code + '\n响应信息:' + result
  205. self.sedMsg.sendMsg(msg)
  206. # header = 'Access-Control-Allow-Credentials: true\nAccess-Control-Allow-Origin: http://test-hcz-static.pingan.com.cn\naccessToken: 8b1f056249134c4f9fb7b573b25ce08c'
  207. # _isOK, headers = format_headers(header)
  208. # print(headers, type(headers))

            

十一、工厂封装(核心)-methodFactoy.py

       1、工厂的逻辑

             工厂是公共方法调用入口。

             有人传入关键的文字,而工厂会找到关键文字对应的公共方法,执行方法得到结果,最后返回给调用工厂的人。

             那我们如何通过文字找方法呢?

             需要用到配置文件,我们在配置文件将前面小节封装的公共方法逐一配置好,

             格式:文字=方法名称

             如下图:

             

             代码实现,先通过配置文件得到公共方法的名称 ,然后使用getattr方法在公共模块对象上找到公共方法,然后执行方法,可以得到想要的结果。

             如下图

             

如果是在用例里执行,以这种格式写

打印结果与报告展示

 

这个需要将后面的封闭与用例解析写完才能看到的效果,加油 

       2、封装

            

            methodFactory.py的代码段

  1. from common.getConfig import myCof
  2. from kemel.commKeyword import CommKeyword
  3. # #初始化公共方法模块
  4. # comKey = CommKeyword()
  5. # #获取所有公共方法配置参数
  6. # comKW = dict(myCof.items('commkey'))
  7. # #获取 取用例方法名称
  8. # method_Name = comKW['获取当前执行用例']
  9. # #通过getattr 获取公共方法模块的对应的模块
  10. # func = getattr(comKey, method_Name, None)
  11. # #执行前面获取的公共方法得到用例
  12. # cases = func(aa=None)
  13. # #打印用例
  14. # print(cases)
  15. class MethodFactory(object):
  16. def __init__(self):
  17. self.comKey = CommKeyword()
  18. self.comKW = dict(myCof.items('commkey'))
  19. def method_factory(self, **kwargs):
  20. """
  21. 用例公共方法工厂
  22. 默认传的参数为:
  23. result:用来接收结果的变量,格式可为${abc}
  24. method:调用的方法,这里设计方法都使用中文
  25. param_x:参数,数量不限。格式可为${abc}会替换为已存在的数据
  26. """
  27. if kwargs.__len__() > 0:
  28. try:
  29. kwargs['method']
  30. except KeyError:
  31. return False, 'keyword:用例[method]字段方法没参数为空.'
  32. try:
  33. method = self.comKW[str(kwargs['method']).lower()]
  34. except KeyError:
  35. return False, 'keyword:方法[' + kwargs['method'] + '] 不存在,或未配置.'
  36. else:
  37. return False, '没有传参'
  38. try:
  39. func = getattr(self.comKey, method, None)
  40. _isOk, reselt = func(**kwargs)
  41. return _isOk, reselt
  42. except Exception as e:
  43. return False, 'keyword:执行失败,估计不存在,异常:' + str(e)
  44. # fac = MethodFactory()
  45. # print(fac.method_factory(method='获取当前用例文件名称'))

十二、解析用例(核心)-testCase.py

       1、详情讲解

            python 自动了一个单元测试框架unittest,用来做自动化测试是绝好的。

           先引用一段理论:

--------------------------------------------

做过自动化测试的同学应该都知道python中的unittest框架,它是python自带的一套测试框架,学习起来也相对较容易,unittest框架最核心的四个概念:

​  test case:就是我们的测试用例,unittest中提供了一个基本类TestCase,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例;unittest中测试用例方法都是以test开头的,且执行顺序会按照方法名的ASCII值排序。

​  test fixure:测试夹具,用于测试用例环境的搭建和销毁。即用例测试前准备环境的搭建(SetUp前置条件),测试后环境的还原(TearDown后置条件),比如测试前需要登录获取token等就是测试用例需要的环境,运行完后执行下一个用例前需要还原环境,以免影响下一条用例的测试结果。

​  test suite:测试套件,用来把需要一起执行的测试用例集中放到一块执行,相当于一个篮子。我们可以使用TestLoader来加载测试用例到测试套件中。

​  test runner:用来执行测试用例的,并返回测试用例的执行结果。它还可以用图形或者文本接口,把返回的测试结果更形象的展现出来,如:HTMLTestRunner。

--------------------------------------------

           自动化测试流程:是基于unittest中TestCase + ddt data 模式成自动化用例集(俗称数据驱动)。而后被unittest中的test suite套件将用例集中管理起来,最后使用unittest中的test runner将集中起来的用例执行,生成测试报告

            解析用例。就是数据驱动这一段。已经封装好在testCase.py中了可以自行看代码与注释学习

       2、封装

            testCase.py代码段

  1. import unittest
  2. import json
  3. from library.ddt import ddt, data
  4. from common.log import mylog
  5. from kemel.methodFactory import MethodFactory
  6. isOK = True
  7. e = Exception()
  8. @ddt #引用数据驱动装饰器
  9. class TestCase(unittest.TestCase):
  10. metFac = MethodFactory() #初始化工厂类
  11. isOK, cases = metFac.method_factory(method='获取当前执行用例')
  12. isOK, fileName = metFac.method_factory(method='获取当前用例文件名称')
  13. if isOK is False:
  14. mylog.error('获取用例失败')
  15. quit()
  16. #调用工厂公共方法入口
  17. def _opear_keyword(self, **kwargs):
  18. return self.metFac.method_factory(**kwargs)
  19. #断言方法
  20. def _assert_res_expr(self, rules, reponse, expr):
  21. """
  22. 断言方法
  23. :param rules:结果包含、结果等于、结果状态
  24. :param res:
  25. :param expr:
  26. :return:
  27. """
  28. try:
  29. res = reponse.json()
  30. except Exception:
  31. res = reponse.text
  32. headers = reponse.headers
  33. code = str(reponse.status_code)
  34. _reason = 'success'
  35. if rules == '结果包含':
  36. if type(expr) is str:
  37. res = json.dumps(res, ensure_ascii=False)
  38. print_result = json.dumps(json.loads(res), sort_keys=True, indent=2, ensure_ascii=False)
  39. else:
  40. print_result = res
  41. try:
  42. self.assertIn(expr, res)
  43. except AssertionError as e:
  44. _isOk = False
  45. _reason = '结果:\n【' + print_result + '】\n 不包含校验值:\n 【' + expr + '】'
  46. else:
  47. _isOk = True
  48. _reason = '结果:\n【' + print_result + '】\n 包含有校验值:\n 【' + expr + '】'
  49. elif rules == '结果等于':
  50. if type(expr) is str:
  51. res = json.dumps(res, ensure_ascii=False)
  52. print_result = json.dumps(json.loads(res), sort_keys=True, indent=2, ensure_ascii=False)
  53. else:
  54. print_result = res
  55. try:
  56. self.assertEqual(expr, res)
  57. except AssertionError as e:
  58. _isOk = False
  59. _reason = '结果:\n【' + res + '】\n 不等于校验值:\n 【' + expr + '】'
  60. else:
  61. _isOk = True
  62. _reason = '结果:\n【' + res + '】\n 等于校验值:\n 【' + expr + '】'
  63. elif rules == '结果状态':
  64. try:
  65. self.assertEqual(expr, code)
  66. except AssertionError as e:
  67. _isOk = False
  68. _reason = '结果:\n【' + code + '】\n 不等于校验值:\n 【' + expr + '】'
  69. else:
  70. _isOk = True
  71. _reason = '结果:\n【' + code + '】\n 等于校验值:\n 【' + expr + '】'
  72. elif rules == '头部包含':
  73. if type(expr) is str:
  74. headers = json.dumps(headers, ensure_ascii=False)
  75. print_header = json.dumps(json.loads(headers), sort_keys=True, indent=2, ensure_ascii=False)
  76. else:
  77. print_header = headers
  78. try:
  79. self.assertIn(expr, headers)
  80. except AssertionError as e:
  81. _isOk = False
  82. _reason = '结果头:\n【' + print_header + '】\n 不包含校验值:\n 【' + expr + '】'
  83. else:
  84. _isOk = True
  85. _reason = '结果头:\n【' + print_header + '】\n 包含有校验值:\n 【' + expr + '】'
  86. elif rules == '头部等于':
  87. if type(expr) is str:
  88. headers = json.dumps(headers, ensure_ascii=False)
  89. print_header = json.dumps(json.loads(headers), sort_keys=True, indent=2, ensure_ascii=False)
  90. else:
  91. print_header = headers
  92. try:
  93. self.assertEqual(expr, headers)
  94. except AssertionError as e:
  95. _isOk = False
  96. _reason = '结果头:\n【' + print_header + '】\n 不等于校验值:\n 【' + expr + '】'
  97. else:
  98. _isOk = True
  99. _reason = '结果头:\n【' + print_header + '】\n 等于校验值:\n 【' + expr + '】'
  100. return _isOk, _reason
  101. #打印用例信息与执行结果,因为是TestCase,最终它们将展示到测试报告中,所以设计输出格式,让报告美美达
  102. def postPinrt(self, **case):
  103. if case['interface'] is not None:
  104. print('\n------------------------------------------------------------------\n')
  105. print('接口:【' + case['interface'] + '】')
  106. if case['method'] is not None:
  107. print('类型:【' + case['method'] + '】')
  108. if case['data'] is not None:
  109. print('参数:【' + case['data'] + '】')
  110. if 'get' == str.lower(case['method']):
  111. if '?' in str(case['url']):
  112. url = str(case['url'])
  113. a, param = url.split('?')
  114. if param is not None:
  115. print('参数:【')
  116. datalist = str(param).split('&')
  117. for data in datalist:
  118. print(data)
  119. print('】')
  120. else:
  121. print('【没带参数】')
  122. print('\n------------------------------------------------------------------\n')
  123. @data(*cases) #,数据驱动装饰器,将用例list中的元素出来,将元素传递给test_audit的case中
  124. def test_audit(self, case):
  125. _isOk = True
  126. #如果interface为commfun,将调用对应的公共方法
  127. if case['interface'] == 'commfun':
  128. """
  129. 如果接口是公共方法,那么字段如下
  130. method:公共方法名
  131. title: 返回接果
  132. url:参数
  133. data:参数 ...暂时四个参数
  134. """
  135. _isOk, _strLog = self._opear_keyword(method=case['method'],
  136. result=case['title'],
  137. param_1=case['url'],
  138. param_2=case['headers'],
  139. param_3=case['data'],
  140. param_4=case['validaterules'])
  141. else:
  142. rows = case['case_id'] + 1
  143. title = case['title']
  144. expect = str(case['expected'])
  145. #发送请求,用例文件里interface不等于commfun,method为post或get的将被执行
  146. _isOK, result = self.metFac.method_factory(**case)
  147. if _isOk:
  148. response = result
  149. code = str(response.status_code)
  150. try:
  151. res = json.dumps(response.json())
  152. self.metFac.method_factory(method='设置变量', result='response', param_1=response) #返回json存
  153. except ValueError:
  154. res = response.text
  155. self.metFac.method_factory(method='设置变量', result='response', param_1=res) #返回html 或xml、 txt存
  156. if case['validaterules'] is None:
  157. _isOk = True
  158. _strLog = '用例[' + str(case['case_id']) + ']:[' + title + ']执行完成.'
  159. else:
  160. rules = case['validaterules']
  161. _isOk, _reason = self._assert_res_expr(rules, response, expect)
  162. if _isOk:
  163. _strLog = '用例[' + str(case['case_id']) + ']:[' + title + ']执行通过. \n 校验结果:\n' + _reason
  164. else:
  165. _strLog = "用例[" + str(case['case_id']) + ']:[' + title + ']执行不通过.\n 原因:\n' + _reason
  166. #报错的接口,给企业微信发送信息
  167. self.metFac.method_factory(title=title, method='发送消息', api=case['interface'], url=case['url'], code=code, result=res)
  168. else:
  169. _strLog = "用例[" + str(case['case_id']) + ']:[' + title + ']执行不通过. \n 原因:\ n' + result
  170. if _isOk:
  171. mylog.info(_strLog)
  172. print(_strLog)
  173. self.postPinrt(**case)
  174. else:
  175. mylog.error(_strLog)
  176. print(_strLog)
  177. self.postPinrt(**case)
  178. raise

十三、最后的运行文件-testRun.py

        1、代码封装

             testRun.py代码段

            写完这个文件的代码,再然后按照第三小节的   ’读取用例模块-getCase.py‘ 里面的规则设计测试用例,然后放支data文件目录里,将配置文件里的用例文件名称配置好,然后执行自动化测试了

  1. import unittest
  2. import os
  3. from common.initPath import CASEDIR, REPORTDIR
  4. from kemel.methodFactory import MethodFactory
  5. from library.HTMLTestRunnerNew import HTMLTestRunner
  6. class TestRun(object):
  7. metFac = MethodFactory()
  8. def __init__(self):
  9. self.suit = unittest.TestSuite()
  10. load = unittest.TestLoader()
  11. self.suit.addTest(load.discover(CASEDIR))
  12. self.runner = HTMLTestRunner(
  13. stream=open(os.path.join(REPORTDIR, 'report.html'), 'wb'),
  14. title='接口自动化测试报告',
  15. description='代替手动冒烟、手动回归,做更精准的测试',
  16. tester='HunWei'
  17. )
  18. def excute(self):
  19. self.runner.run(self.suit)
  20. self.metFac.method_factory(method='发送邮件')
  21. if __name__=='__main__':
  22. run = TestRun()
  23. run.excute()
  24. # from kemel.methodFactory import MethodFactory
  25. # abc = MethodFactory()
  26. # isOK, cases = abc.method_factory(method='获取当前执行用例')
  27. # print(cases)

            执行后的测试报告如下:

   

              建议收藏慢慢看,希望诸位点个小赞! 哈哈~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/820929
推荐阅读
相关标签
  

闽ICP备14008679号