该项目有助于进一步了解自动化测试
适用于普遍中型企业接口自动化框架
适合有一定Python编程语言基础,了解函数式编程;会使用Pycharm开发工具。
~~为了方便同学们进一步了解接口自动化测试,该项目基于考试星在线考试系统进行接口自动化测试。
提前注册账号
熟悉里面的问卷模块业务功能(玩玩!)
打开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 json
HTTP_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 用户名
companyId
newCompanyId
password
passwordMD5 这些参数通过抓取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)
# 获取登录后的cookie
self.cookies = requests.utils.dict_from_cookiejar(response.cookies)
# session_id 用于登录login_check
self.session_id = self.cookies['sessionId']
# 把获取的cookie值进行拼接
cookie = ""
for i in self.cookies:
cookie = cookie + i + '=' + self.cookies[i] + ';'
# 把获取的cookie与本地存储的cookie进行拼接
self.cookies = cookie
cookies = cookie + config.LOCAL_COOKIE
self.post_cookies = cookie
def 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 token
if __name__ == '__main__':
a = Exam_login(user_name="xxxx")
print(a.post_cookies)
token = a.login_check()
print(token)
import requests
def exam_request(method, param, headers, url):
"""
考试星接口二次封装,考试星大部分是POST请求
:param method: 请求方法
:param param: 参数
:param headers: 请求头
:param url: 请求地址
:return: 数据
"""
if len(param)<= 0:
raise AssertionError("参数不能为空")
# 如果是get请求,拼接url
if method == "GET" or method == "get":
get_param = ""
for key in param:
get_param = get_param + "{}={}&".format(key, param[key])
url = url + "?" + get_param
payload = {}
response = requests.request("GET", url, headers=headers, data=payload)
else:
response = requests.request("POST", url, headers=headers, data=param)
return response
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"
"""用来读取数据"""
import yaml
import json
from configparser import ConfigParser
class MyConfigParser(ConfigParser):
# 重写configparser 中的optionxform 函数 解决.ini 文件自动转小写的问题
def __init__(self, defaults=None):
ConfigParser.__init__(self, defaults=defaults)
def optionxform(self, optionstr: str) -> str:
return optionstr
class 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 data
data = 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_data
api_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_get
class 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: '期末考试'
# 移动文件夹id
target_id: '15688454'
# 删除返回断言
errorcode: 0
"""
def test_create_dir_rename_move_delete(testcase_data):
pass
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"
"""
封装log日志控制台打印输出
"""
import datetime
import logging
import os
def 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()
# -*- 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)
# 获取分类id
request.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"] == 200
if __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 None
assert 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 == 200
if __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_get
class 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 == code
if __name__ == '__main__':
pytest.main([__file__ ,'-s'])
# 单脚本测试生成allure报告
# pytest.main([__file__, '-s', '-q', '--alluredir', './result'])
# os.system('allure generate ./result -o ./report --clean')
# 定义生成测试报告的方法
def create_allure_report(xml_report_path, html_report_path):
"""生成Allure测试报告"""
# 自定义shell
cmd = "allure generate %s -o %s --clean"%(xml_report_path, html_report_path)
try:
os.system(cmd)
except Exception:
raise Exception("执行用例失败,请检查环境配置")