最近文章一直都是python的第三方库使用及爬虫的知识,针对自动化测试的优化版本也没有及时发布出来,今天主要抽时间整理了一下,罗列了运行流程及项目工程目录。

所提供的框架仅供参考,中间还有很多不足之处,也希望大家踊跃提出疑义和建议。

下面进入代码的世界……

工程目录

apiTest
├─apiInterface
├─cases
├─common
├─config
├─dynamicData
├─logs
├─reports
│  ├─allure
│  └─html
├─runMain
├─testDatas
├─requirements.txt
└─settings.py
  • apiTest:根目录

  • apiInterface:是存放一些url路径,这里只是返回路径,未做多余操作

  • cases:测试用例存放文件夹

  • common:存放一些公共调用的类

  • config:配置文件存放目录

  • dynamicData:动态参数存放处,就是指一些接口的上下游需要的参数

  • logs:存放程序运行的日志文件

  • reports:存放程序运行完成,生成的测试报告,分为allure和html报告

  • runMain:存放程序运行入口的目录

  • testDatas:测试数据目录,主要是yaml文件

  • requirements.txt:程序需要的依赖包

  • settings.py:配置文件,路径,环境切换及各类配置的存放,类似django的setting文件

框架的运行流程

主要是运用了python、request、pytest、yaml、Jinja2、allure组成的测试框架.

其运行流程就是执行测试用例时,会先拿接口路径,其次再读取yaml文件,然后yaml文件替换需要替换的动态数据,再然后就是接口拿到数据去利用python++pytest+requests库去请求,然后根据返回值进行断言及生成allure报告。

实际代码介绍

apiTest/apiInterface/apiMatch.py

# -*-coding:utf-8 -*-
# ** createDate: 2021/11/16 11:37
# ** scriptFile: apiMatch.py
# ** __author__: Li Feng
"""
注释信息:
"""__all__ = ["api_match"]class _ApiMatch:@propertydef match_european_cup(self):"""2021欧洲杯赛程查询2021欧洲杯赛程详细信息:return:"""return "/fapig/euro2020/schedule"api_match = _ApiMatch()

apiTest/cases/test_european_cup.py

# -*- encoding: utf-8 -*-
"""
@__Author__: lifeng
@__Software__: PyCharm
@__File__: test_european_cup.py
@__Date__: 2021/6/13 19:00
"""import pytest
from common.readRenderYaml import render
from apiInterface.apiMatch import api_match
from dynamicData.matchDynamic import contentsclass TestNews:# 读取yaml文件,并执行数据替换(contents=contents就是接收需要替换的参数)data = render("api_match", "test_european_cup", contents=contents)@pytest.mark.parametrize('test_data, title, results', data["test_data"])def test_european_cup(self, test_data, title, results, auth):"""2021欧洲杯赛程:param test_data:   测试数据:param title:       传参名称:param results:     预期结果:param auth:        登录后返回一个请求对象:return:"""response = auth.send_get(api_match.match_european_cup, test_data)assert response["reason"] == results["reason"]assert type(response["result"]["data"]) == type(results["result"]["data"])if __name__ == '__main__':pytest.main(["-v", "-s", "test_european_cup.py"])

apiTest/common/readRenderYaml.py

在cases目录中的test_european_cup.py文件中调用的render函数就是下面的这个类提供的。

它的只要功能就是读取yaml文件然后执行Jinja2库进行动态参数替换。

# -*-coding:utf-8 -*-
# ** createDate: 2021/11/16 8:18
# ** scriptFile: readRenderYaml.py
# ** __author__: Li Feng
"""
注释信息:
"""import json
import yaml
import jinja2
from common.mapMnvironment import MapEnvironment__all__ = ["render"]class _ReadYamlRender:def __init__(self, yaml_path_name: str, yaml_tier: str, contents: dict = None):self._content = contents"""读取yaml文件的数据, 返回正经json数据"""with open(yaml_path_name, encoding="utf-8") as y:data = yaml.safe_load(y)# json.dumps要把字符串数据转成正经json数据,用于return返回时不报错self._template_name = json.dumps(data[yaml_tier])@propertydef render(self):"""利用jinja2进行动态数据渲染替换,返回字典类型:return:"""if self._content is not None:jinja2_data = jinja2.Template(self._template_name).render(self._content)return json.loads(jinja2_data)else:return json.loads(self._template_name)def render(path_key: str, yaml_tier: str, contents: dict = None):"""执行读取yaml文件并渲染返回数据:param path_key::param yaml_tier::param content::return:"""# 获取所有yaml文件路径data = MapEnvironment().yaml_path# 渲染yaml文件return _ReadYamlRender(data[path_key], yaml_tier, contents).render

apiTest/common/sendRequest.py

在cases目录中的test_european_cup.py文件中调用的auth.send_get方法就是下面的这个请求类提供的。

它的只要功能就是进行接口的请求,可能你会疑问为什么是auth。seng_get,那是因为我这里用了pytest框架提供的测试夹具功能(这个后面会单独说pytest框架,在本篇文章了解下即可)。

import json
import urllib3
import requests
from functools import wraps
from requests import exceptions
from requests_toolbelt import MultipartEncoder
from common.logLogging import do_logger
from common.mapMnvironment import MapEnvironment__all__ = ["send"]def _handle_response(func):"""处理请求后的返回值:param func: 传入函数:return:"""@wraps(func)def wraps_response(*args, **kwargs):results = func(*args, **kwargs)request_body = results.request.bodyrequest_url = results.request.urltry:if results.ok:return results.json()except json.JSONDecodeError:return results.text.encode("utf-8")except Exception as _error:do_logger.error(f"接口请求出错:"f"请求url:{request_url},"f"请求参数:{request_body},"f"返回数据:{results.text}")raise exceptions.RequestException from _errorreturn wraps_responsedef _print_url(r, *args, **kwargs):"""回调函数,r接受一个数据块作为它的第一个参数:param r::param args::param kwargs::return:"""print(f"请求url:{r.request.url}")print(f"请求参数:{r.request.body}")# print(f"请求数据:{r.request.prepare()}")print(f"返回数据:{r.text}")class _SendRequest:_map = MapEnvironment()def __init__(self):urllib3.disable_warnings()self.s = requests.Session()self.s.verify = Falseself.headers = self.s.headersself.headers.update(MapEnvironment().headers)urllib3.disable_warnings(urllib3.exceptions.InsecurePlatformWarning)@classmethoddef _get_url(cls, url):"""拼接url,增加platform参数:param url::return:"""return cls._map.base_url(cls._map.host) + urldef send_upload(self, url, filename, filetype='application/vnd.ms-excel'):"""上传文件请求:param url::param filename: 文件名称:param filetype: 文件类型:return:"""try:url = self._get_url(url)with self.s as interface:from pathlib import Pathm = MultipartEncoder(fields={'file': (filename, open(Path().parent.joinpath("upload"), 'rb'), filetype)})self.s.headers.update({"Content-Type": m.content_type})response = interface.post(url=url, data=m, hooks=dict(response=_print_url))results = json.loads(json.dumps(response.text))except Exception as e:do_logger.error(e)raise (ImportError, FileNotFoundError, PermissionError) from eelse:return resultsdef send_download(self, url, filename, params=None, **kwargs):"""下载文件请求:param url::param filename: 文件的名称加后缀名(例:name.xlsx):param params::param kwargs::return:"""url = self._get_url(url)with self.s as interface:response = interface.get(url=url, params=params,hooks=dict(response=_print_url), **kwargs)try:from pathlib import Path, PurePathif response.ok:with open(PurePath(Path(__file__).parent).parent.joinpath("download", filename), 'wb') as save:for chunk in response.iter_content():save.write(chunk)except Exception as e:do_logger.error(e)raise (ImportError, FileNotFoundError, PermissionError) from eelse:return True@_handle_responsedef send_get(self, url, params=None, **kwargs):"""get请求:param url::param params::param kwargs: 动态参数:return: 返回状态码"""url = self._get_url(url)with self.s as interface:response = interface.get(url, params=params,hooks=dict(response=_print_url), **kwargs)return response@_handle_responsedef send_post(self, url, json=None, data=None, query=None, **kwargs):"""post请求:param url::param json::param data::param query:   接收url跟随的参数:param kwargs:  动态参数:return: 返回状态码"""url = self._get_url(url)with self.s as interface:response = interface.post(url=url, data=data,json=json, params=query,hooks=dict(response=_print_url), **kwargs)return response@_handle_responsedef send_put(self, url, json=None, data=None, query=None, **kwargs):"""put请求:param url::param json::param data::param query:   接收url跟随的参数:param kwargs:  动态参数:return: 返回状态码"""url = self._get_url(url)with self.s as interface:response = interface.put(url=url, data=data,json=json, params=query,hooks=dict(response=_print_url), **kwargs)return response@_handle_responsedef send_delete(self, url, **kwargs):"""delete请求:param url::param kwargs:  动态参数:return: 返回状态码"""url = self._get_url(url)with self.s as interface:response = interface.delete(url=url,hooks=dict(response=_print_url), **kwargs)return response# 创建对象
send = _SendRequest()

apiTest/dynamicData/matchDynamic.py

在cases目录中的test_european_cup.py文件中可以看到render(xx, xx, contents=contents),它就是把需要替换的动态参数传给Jinja2去执行替换操作。

# -*-coding:utf-8 -*-
# ** createDate: 2021/11/16 11:40
# ** scriptFile: matchDynamic.py
# ** __author__: Li Feng
"""
注释信息:
"""
__document__ = """存放一些动态数据,用于yaml文件中的数据替换操作"""
contents = {"key": "9d0dfd9dbaf51de283ee8a88e58e332b"
}

apiTest/logs

存放程序运行时,出现错误的日志。

apiTest/reports

存放allure和html报告目录,allure生成的是json文件,所有尽量再建一个子文件夹。

apiTest/runMain/main.py

这里就是执行pytest,运行全部用例,然后生成allure报告和html报告,并存放在reports目录中。

# -*-coding:utf-8 -*-
# ** createDate: 2021/11/16 11:31
# ** scriptFile: main.py
# ** __author__: Li Feng
"""
注释信息:
"""import os
import sys
import pytest# 执行路径插入操作,增强代码的可移植性
sys.path.insert(0, os.path.dirname(os.path.dirname(os.getcwd())))print(sys.path)def main():# 入口函数,运行全部用例,生成html和allure报告pytest.main(['../cases/', '--html=../reports/report.html','--alluredir=../reports/allure/allure-report'])if __name__ == '__main__':main()

apiTest/testDatas

存放yaml文件,yaml文件中主要是放一些测试数据,针对动态的测试数据,要根据Jinja2的语法来使用:

test_european_cup:test_data:# 接口参数- - type: 1key: "{{key}}"# 接口传参名称- name: type字段传1# 实际结果,用于断言操作- reason: "查询成功!"result:data:-- - type: 2key: "{{key}}"# 接口传参名称- name: type字段传2# 实际结果,用于断言操作- reason: "查询成功!"result:data:-- - type: 3key: "{{key}}"# 接口传参名称- name: type字段传3# 实际结果,用于断言操作- reason: "查询成功!"result:data:-

{{key}}这里的意思就是取key的value值,而value值就是apiTest/dynamicData/matchDynamic.py文件中提供的:

# -*-coding:utf-8 -*-
# ** createDate: 2021/11/16 11:40
# ** scriptFile: matchDynamic.py
# ** __author__: Li Feng
"""
注释信息:
"""
__document__ = """存放一些动态数据,用于yaml文件中的数据替换操作"""
contents = {"key": "9d0dfd9dbaf51de283ee8a88e58e218b"
}

apiTest/requirements.txt

就是你用的一些依赖包

allure-pytest
allure-python-commons
Appium-Python-Client
beautifulsoup4
jmespath
jsonpath
jsonschema
mysqlclient==1.4.6
openpyxl
pyaml
PyMySQL
pytest
pytest-base-url
pytest-cov
pytest-cover
pytest-emoji
pytest-html
pytest-metadata
pytest-rerunfailures
pytest-xdist
python-jenkins
PyYAML
redis
requests
requests-toolbelt
selenium
pytest-pikachu
pytest-clarity

apiTest/settings.py

它主要就是一个配置文件,配置账号密码,日志,环境变量等:

# -*-coding:utf-8 -*-
# ** createDate: 2021/11/16 8:12
# ** scriptFile: setting.py
# ** __author__: Li Feng
"""
注释信息:
"""from pathlib import Path, PurePath# 获取项目根目录
BASE_DIR = PurePath(Path(__file__).parent)
print(BASE_DIR)# 用于判断是否往企业微信发送测试报告:True是发送、False是不发送
IS_SEND = True# 设置运行的环境变量
ENVIRONMENT = "PRO"  # 环境变量值分别为  测试:TEST;预发布:PRE;生产:PRO# 接口请求域名
HOST = "http://apis.juhe.cn"
# 设置头信息指定域名和Content-Type类型
HEADERS = {'Content-Type': 'application/json'}# 环境IP配置
BASE_HOST = {"test": None,"pre": None,"pro": None,
}# 数据库配置
DATABASES = {"pro": {"host": "8.136.250.157", "port": 1234, "user": "root", "passwd": "test.2016", "db": "testing"},
}# yaml文件路径
YAML_FILE_PATH = {"api_idiom": BASE_DIR.joinpath("testDatas", "idiom_modules.yml"),"api_match": BASE_DIR.joinpath("testDatas", "match_modules.yml"),
}# 日志存放目录
LOGGING_PATH = BASE_DIR.joinpath("logs", f"logfile.text")# 日志记录配置
LOGGING_CONFIG = {"version": 1,"root": {"level": "DEBUG","handlers": ["file", "console"]},"handlers": {"console": {"class": "logging.StreamHandler","level": "ERROR","formatter": "console_formatters"},"file": {"class": "logging.handlers.RotatingFileHandler","formatter": "file_formatters","filename": LOGGING_PATH,"level": "DEBUG","maxBytes": 100,"backupCount": 5,"encoding": "utf-8"}},"formatters": {"console_formatters": {"format": "%(asctime)s [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s","datefmt": "%Y%m%d %H:%M:%S"},"file_formatters": {'format': "%(asctime)s [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s- %(pathname)s","datefmt": "%Y%m%d %H:%M:%S"}}
}

从配置文件中可以清晰看到日志的配置、环境的配置、还有发送邮件的配置等,这些都需要公共方法调用的,后续会给补充上来,以上就是近期对项目框架的一些优化,后续会把pytest框架的使用及对应的三方库整理出来,allure报告的使用也会整理出来,会做成一套接口自动化教程。

可能中间还有很多不好之处,也会慢慢改善进行优化,一个阶段的努力也是一个阶段的提升,总结这个阶段的结果,是我们追求的星辰大海,哪怕它很慢

以上总结或许能帮助到你,或许帮助不到你,但还是希望能帮助到你,如有疑问、歧义,直接私信留言会及时修正发布;非常期待你的赞赏、点赞、在看+分享哟,谢谢!

下面是一份配套资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

最后: 可以在公众号:伤心的辣条 ! 免费领取一份216页软件测试工程师面试宝典文档资料。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

学习不要孤军奋战,最好是能抱团取暖,相互成就一起成长,群众效应的效果是非常强大的,大家一起学习,一起打卡,会更有学习动力,也更能坚持下去。你可以加入我们的测试技术交流扣扣群:914172719(里面有各种软件测试资源和技术讨论)

喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!


好文推荐

转行面试,跳槽面试,软件测试人员都必须知道的这几种面试技巧!

面试经:一线城市搬砖!又面软件测试岗,5000就知足了…

面试官:工作三年,还来面初级测试?恐怕你的软件测试工程师的头衔要加双引号…

什么样的人适合从事软件测试工作?

那个准点下班的人,比我先升职了…

测试岗反复跳槽,跳着跳着就跳没了…

python:接口自动化测试框架优化版相关推荐

  1. python 接口自动化测试框架有哪些_Python接口自动化测试框架

    Python接口自动化测试框架 在自动化的测试体系中,包含了UI自动化测试和接口自动化测试,UI自动化实现的前提是软件版本进入稳定期,UI界面稳定.变动少,相比较之下接口自动化,接口受外界因素的影响较 ...

  2. 11小时 python自动化测试从入门到_从设计到开发Python接口自动化测试框架实战,资源教程下载...

    课程名称 从设计到开发Python接口自动化测试框架实战,资源教程下载 课程简介: 课程从接口基础知识入门,从抓包开始,到接口工具的运用,再到常见接口库.接口开发.Mock服务.unittest框架的 ...

  3. python接口自动化测试框架实战从设计到开发_Python接口自动化测试框架实战 从设计到开发...

    第1章 课程介绍(不要错过) 本章主要讲解课程的详细安排.课程学习要求.课程面向用户等,让大家很直观的对课程有整体认知! 第2章 接口测试工具Fiddler的运用 本章重点讲解如何抓app\web的h ...

  4. python接口自动化测试框架实战从设计到开发_【B0753】[java视频教程]Python接口自动化测试框架设计到开发完整版视频教程 it教程...

    Java视频教程名称:Python接口自动化测试框架设计到开发完整版视频教程   java自学网[javazx.com]  Python视频教程   it教程 Java自学网收集整理 java论坛&q ...

  5. python接口自动化测试框架介绍

    之前在项目中搞了一套jmeter +jenkins + git + ant接口自动化测试框架,在项目中运行了大半年了,效果还不错, 最近搞了一套requests + unittest + ddt +p ...

  6. python接口自动化测试框架pdf,Python接口自动化测试框架设计到开发完整版2019

    1:课程详细介绍.mp4 ; k& X* V: X! X% \; ]; u- V$ z7 L) h: C; h2:课程答疑.mp4+ J  K* q0 O  x + j( v2 n7 B7 s ...

  7. Python接口自动化测试框架(工具篇)-- 接口测试工具HTTPRUNNER

    文章目录 前言 HttpRunner介绍 如何安装 如何使用 小结 扩展 回到刚开始的问题 前言 又又又到了讲httprunner框架的时候,作为测试界的后起之秀,从其开源默默无闻到家喻户晓,从线上教 ...

  8. 基于Python接口自动化测试框架(初级篇)附源码

    目录 1.引言 1.框架设计思路 2.框架设计思路 3.运行程序 4.源码获取 引言 很多人都知道,目前市场上很多自动化测试工具,比如:Jmeter,Postman,TestLink等,还有一些自动化 ...

  9. Python接口自动化测试框架(工具篇)-- 接口测试工具JMeter

    文章目录 前言 JMeter 环境安装 JMeter接口测试案例 快速创建JMeter接口测试计划 扩展 接口自动化测试 性能自动化测试 前言 相比使用postman做接口测试,作为测试人员,熟练使用 ...

最新文章

  1. ∇SLAM:自动可微分SLAM
  2. 【转】全排列算法非递归实现和递归实现
  3. SRS流媒体服务器——单机环境搭建和源码目录介绍
  4. java 队列和堆栈_Java中的堆栈和队列
  5. 【正则表达式】IPv4地址的正则匹配
  6. c语言是如何实现泛型链表
  7. 2019暑假绍兴第一中学游记
  8. 如何解决mac在访达里不可以新建txt文本的问题?
  9. Base64,DES,RSA,SHA1,MD5 笔记
  10. Redis4.0之持久化存储
  11. f分布表完整图a=0.01_桥梁横向分布系数计算方法及案例详解
  12. 计算机英语rom是什么意思,涨姿势!电脑里的rom,ram是什么意思?
  13. 电源 PFC(功率因数校正)电路拓扑,共计100多份,内含A PFC,连续断续,交错,维也纳,各功率段的PFC电路,还有电感 设计选型
  14. 关于百度有啊的几点看法
  15. React中文文档之Components and Props
  16. JS(五):JS的window对象之window相关方法、定时器
  17. input标签能换行么?textarea标签属性
  18. 是时候客观评价Retrofit了,Retrofit这几点你必须明白!
  19. k近邻算法,朴素贝叶斯算法,分类模型评估,模型调优
  20. Mac用Option键输入特殊符号

热门文章

  1. RAII(Resource Acquisition Is Initialization:资源获取即初始化)
  2. 【论文写作】2021毕业论文写作选题技巧,建议收藏!
  3. mysql主主同步稳定吗_MySQL主主同步
  4. java插入法排序_java算法之插入排序法
  5. node定时任务php 卡,在NodeJs中如何实现定时任务(详细教程)
  6. Python编译、运行、反编译pyc文件
  7. COM的八个经验和教训
  8. 数组方法 splice()与slice()的区别
  9. Hazelcast更换CEO,承诺继续造福开源社区
  10. scala 学习笔记--集合