文章目录
- 前言
- 一、项目环境搭建
- 二、项目分析
- 三、框架搭建
- 1、解决登录问题、获取token
- 2、熟悉项目的接口请求方式、二次封装requests请求
- 3、缓解业务请求接口参数臃肿
- 4、重新封装logging日志
- 5、通用方法编写
- 四、编写自动化脚本
- 场景业务需求
- 单接口业务需求
- 五、生成测试报告
- 六、写在最后
前言
-
该项目有助于进一步了解自动化测试
-
适用于普遍中型企业接口自动化框架
- Python+Request+Pytest+Yaml+Log+allure+git+邮箱/钉钉+集成Jenkins(本文暂时不写集成Jenkins)
-
适合有一定Python编程语言基础,了解函数式编程;会使用Pycharm开发工具。
一、项目环境搭建
~~为了方便同学们进一步了解接口自动化测试,该项目基于考试星在线考试系统进行接口自动化测试。
-
提前注册账号
-
熟悉里面的问卷模块业务功能(玩玩!)
- 发布问卷
- 查询问卷
- 删除问卷
二、项目分析
- 解决项目登录问题(Cookie持久化)、部分请求操作包含token(获取token)
- 知道项目主要接口请求类型,便于requests请求二次封装
- 如何处理数据(YAML)
- 如何记录日志(Logging)
- 通用方法剥离
- 生成测试报告(Allure)-该博客省略
- 如何发生测试报告(集成邮箱/钉钉)-该博客省略
- 如何持续集成(Jenkins)-该博客省略
三、框架搭建
1、解决登录问题、获取token
打开F12抓取登录等接口,分析请求方式、请求体内容、返回值内容login 接口可以获取Cooke值,获取存储即可cookie持久化操作login_check 接口返回值中有token值,即发起请求(两个参数sessionid和companyId都是login接口返回中有)即可获取
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"import requests
import urllib3
import jsonHTTP_HEADERS = {'authority': "www.kaoshixing.com",'pragma': "no-cache",'Cache-Control': "no-cache",'origin': "https://www.kaoshixing.com",'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",'x-tingyun-id': "YfNlX9ebdkc;r=778649428",'Content-Type': "application/x-www-form-urlencoded;charset=UTF-8",'accept': "application/json, text/javascript, */*; q=0.01",'sec-fetch-dest': "empty",'x-requested-with': "XMLHttpRequest",'sec-fetch-site': "same-origin",'sec-fetch-mode': "cors",'referer': "https://www.kaoshixing.com/login/account/login/262319",'accept-language': "zh-CN,zh;q=0.9",}class Exam_login(object):"""考试星登录方法这里只考虑单账号登录,只把user_name参数化@param user_name 用户名companyIdnewCompanyIdpasswordpasswordMD5 这些参数通过抓取Login接口即可获得"""def __init__(self,user_name):self.user_name = user_name# 处理user_name_input 参数user_name_input = user_name.split('@')[0]login_url = "https://www.kaoshixing.com/login/account/login"data = {"userName": user_name,"userNameInput": user_name_input,"phoneAccount": "","authCode": "","captchaText": "","companyId": "xxxx","newCompanyId": "xxxx","password": "xxxx","passwordMD5": "xxxx","nextUrl": "","remember": "false"}# 警用urllib3 警告urllib3.disable_warnings()response = requests.post(login_url, data=data, headers=HTTP_HEADERS, verify=False)# 获取登录后的cookieself.cookies = requests.utils.dict_from_cookiejar(response.cookies)# session_id 用于登录login_checkself.session_id = self.cookies['sessionId']# 把获取的cookie值进行拼接cookie = ""for i in self.cookies:cookie = cookie + i + '=' + self.cookies[i] + ';'# 把获取的cookie与本地存储的cookie进行拼接self.cookies = cookiecookies = cookie + config.LOCAL_COOKIEself.post_cookies = cookiedef login_check(self):"""考试星登录后验证方法companyId Login接口抓取"""login_check_url = "https://exam.kaoshixing.com/login/public/login_check"data ={"sessionId": self.session_id,"companyId": "xxxx"}# 转为字符串进行传参数data = json.dumps(data)response = requests.request("POST", login_check_url, headers=HTTP_HEADERS, data=data, verify=False)content = json.loads(str(response.content,encoding='utf-8'))# 获取登录token的值token = content['data']['bizContent']['token']return tokenif __name__ == '__main__':a = Exam_login(user_name="xxxx")print(a.post_cookies)token = a.login_check()print(token)
2、熟悉项目的接口请求方式、二次封装requests请求
- 项目中主要是post和get请求,post请求居多
import requestsdef exam_request(method, param, headers, url):"""考试星接口二次封装,考试星大部分是POST请求:param method: 请求方法:param param: 参数:param headers: 请求头:param url: 请求地址:return: 数据"""if len(param)<= 0:raise AssertionError("参数不能为空")# 如果是get请求,拼接urlif method == "GET" or method == "get":get_param = ""for key in param:get_param = get_param + "{}={}&".format(key, param[key])url = url + "?" + get_parampayload = {}response = requests.request("GET", url, headers=headers, data=payload)else:response = requests.request("POST", url, headers=headers, data=param)return response
3、缓解业务请求接口参数臃肿
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24""""用来读取数据"""import yaml
import json
from configparser import ConfigParserclass MyConfigParser(ConfigParser):# 重写configparser 中的optionxform 函数 解决.ini 文件自动转小写的问题def __init__(self, defaults=None):ConfigParser.__init__(self, defaults=defaults)def optionxform(self, optionstr: str) -> str:return optionstrclass ReadFileData():def __init__(self):pass# 读取Yaml数据def load_yaml(self, file_path):with open(file_path, encoding="utf-8") as f:data = yaml.safe_load(f)return data# 读取json数据def load_json(self, file_path):with open(file_path, encoding="utf-8") as f:data = json.load(f)return data# 读取init数据def load_init(self, file_path):config = MyConfigParser()config.read(file_path, encoding="utf-8")data = dict(config._sections)return datadata = ReadFileData()
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"import pytest
import os
from method.read_data import data # 上一个封装读取数据的py文件路径BASE_PATH = os.path.dirname(__file__)def get_data(yaml_file_name):try:data_file_path = os.path.join(BASE_PATH, "data", yaml_file_name)yaml_data = data.load_yaml(data_file_path)except Exception as e:pytest.skip(str(e))else:return yaml_dataapi_data_get = get_data("api_test_get.yaml") # 读取当前目录data下文件名为api_test_get.yaml
api_data_post = get_data("api_test_post.yaml")# 读取当前目录data下文件名为api_test_post.yaml
scenes_data = get_data("scenes_test.yaml")#读取当前目录data下文件名为scenes_test.yaml
单接口参数通过@pytest.mark.parametrize()传递参数即可,场景测试参数需要写个模块级别的请求fixtures
"""
针对于单接口
yaml数据为
test_right:# 考试口令,返回码- ['EDBHET', 0]
"""from Test_exam.conftest import api_data_getclass Test_get_demo():"""获取考试列表"""@allure.testcase(".....")@allure.description("test_get_demo")@pytest.mark.parametrize("name, result",api_data_get["test_get_demo"])def test_get_demo(self,name, result):
"""
对于场景,Yaml参数为
# 新建我的考试文件夹-重命名-移动文件夹-删除
test_create_dir_rename_move_delete:# 创建考试名称new_name: '期中考试'# 修改考试名称change_name: '期末考试'# 移动文件夹idtarget_id: '15688454'# 删除返回断言errorcode: 0
"""def test_create_dir_rename_move_delete(testcase_data):pass
4、重新封装logging日志
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24""""
封装log日志控制台打印输出
"""import datetime
import logging
import osdef init_log(log_level=logging.INFO, log_dir =''):if log_dir =='':logging.basicConfig(level=log_level,format="%(asctime)s-%(name)s-%(levelname)s-%(message)s",datefmt= '%m-%d %H:%M:%S')# 此处省略文件记录日志方法方法def debug(tag, msg):_logger = logging.getLogger(tag)_logger.debug(msg)def info(tag, msg):_logger = logging.getLogger(tag)_logger.info(msg)def warning(tag, msg):_logger = logging.getLogger(tag)_logger.warning(msg)def error(tag, msg):_logger = logging.getLogger(tag)_logger.error(msg)def critical(tag, msg):_logger = logging.getLogger(tag)_logger.error(msg)init_log()
5、通用方法编写
- 创建13时间戳
- 生成Allure测试报告
…
四、编写自动化脚本
场景业务需求
- 管理员创建问卷->列表查询问卷->用户答卷提交->获取用户问卷信息->删除问卷
- 问卷分类新增->修改->删除
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"from method.exam_request import Exam_request
from method import config
import allure
import pytest@allure.description("创建分类_修改分类_删除分类")
@allure.title("test_create_category_change_and_delete")
def test_create_category_change_and_delete(testcase_data):# 实例化request = Exam_request()# 登录账号request.account_login(user_name=config.MASTER_ACCOUNT)# 获取分类idrequest.get_classify_id()assert request.pid is not None# 新增分类name = testcase_data["name"]request.add_classify(name)assert request.question_classify_id is not None# 修改分类rename = testcase_data["rename"]update_info = request.update_classify(rename=rename)assert update_info["code"] == 200# 删除分类delete_info = request.delete_classify()assert delete_info["code"] == 200if __name__ == '__main__':pytest.main([__file__,'-s'])
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"from method.exam_request import Exam_request
from method import config
import allure
import pytest@allure.description("创建问卷_查询问卷_用户答卷_获取数据_删除问卷")
@allure.title("test_create_questionnaire_query_user_post_get_data_delete_questionnaire")
def test_create_questionnaire_query_user_post_get_data_delete_questionnaire(testcase_data):# 实例化request = Exam_request()# 登录账号request.account_login(user_name=config.MASTER_ACCOUNT)# 编辑问卷name = testcase_data["name"]introduce = testcase_data["introduce"]question = testcase_data["question"]content = testcase_data["content"]request.edit_the_questionnaire(name=name,introduction=introduce,question=question,content=content)assert request.questioninfotab is not Noneassert request.questioninfo is not None# 发布问卷days = testcase_data["during_days"]request.post_questionnaire(title=name,during_time=days)# 查询问卷question_info = request.query_questionnaire(name=name)assert question_info["code"] == testcase_data["code"]assert question_info["data"]["bizContent"]["rows"][0]["title"] == name# 获取问卷request.get_questionnaire()assert request.questionid is not None# 提交问卷request.submit_questionnaire()# 统计数据request.analysis_questionnaire()# 删除问卷---去了回收站,并未删除request.delete_questionnaire()assert request.code == 200if __name__ == '__main__':pytest.main([__file__,'-s'])
单接口业务需求
- 存在问卷删除
- 不存在问卷删除
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24""""
这里的两个删除只是例子,虽然不影响功能
但任意id值都可以删除,接口并未做校验
"""import pytest
import allure
import os
from method.exam_request import Exam_request
from method import config
from Test_exam.conftest import api_data_getclass Test_delete_questionnaire():"""删除问卷接口"""@allure.testcase("删除问卷接口-存在")@allure.description("Test_delete_questionnaire")@pytest.mark.parametrize("id, code",api_data_get["test_delete_one"])def test_delete_one(self,id, code):# 初始化request = Exam_request()# 登录账号request.account_login(user_name=config.MASTER_ACCOUNT)# 删除问卷request.delete_questionnaire(id,single=True)assert request.code == code@allure.testcase("删除问卷接口-不存在")@allure.description("Test_delete_questionnaire")@pytest.mark.parametrize("id, code",api_data_get["test_delete_two"])def test_delete_two(self,id, code):# 初始化request = Exam_request()# 登录账号request.account_login(user_name=config.MASTER_ACCOUNT)# 删除问卷request.delete_questionnaire(id, single=True)assert request.code == codeif __name__ == '__main__':pytest.main([__file__ ,'-s'])# 单脚本测试生成allure报告# pytest.main([__file__, '-s', '-q', '--alluredir', './result'])# os.system('allure generate ./result -o ./report --clean')
五、生成测试报告
- 自行配置allure环境(windows/mac)
# 定义生成测试报告的方法
def create_allure_report(xml_report_path, html_report_path):"""生成Allure测试报告"""# 自定义shellcmd = "allure generate %s -o %s --clean"%(xml_report_path, html_report_path)try:os.system(cmd)except Exception:raise Exception("执行用例失败,请检查环境配置")
六、写在最后
- 文档主要是带入了解接口自动化的学习(基于Python语言),有些细化的知识点需要自己去索引学习
- 有部分过程文档没有写,需要自己去研究,最后会附上整体项目源码(知识星球内可下载)
- 集成Jenkins也不是特别难,搭建好Jenkins自己捣鼓捣鼓即可集成,下次有时间把Jenkins集成补充完成