new joyhub_backend
This commit is contained in:
0
joyhub_backend/test_case/TestCase/__init__.py
Normal file
0
joyhub_backend/test_case/TestCase/__init__.py
Normal file
0
joyhub_backend/test_case/TestCase/接口/__init__.py
Normal file
0
joyhub_backend/test_case/TestCase/接口/__init__.py
Normal file
33
joyhub_backend/test_case/TestCase/接口/conftest.py
Normal file
33
joyhub_backend/test_case/TestCase/接口/conftest.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
|
||||
current_file_path = os.path.abspath(__file__)
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(current_file_path), '../../../..'))
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
try:
|
||||
import allure_pytest.listener
|
||||
except ImportError:
|
||||
allure_pytest = None
|
||||
|
||||
|
||||
def _allure_test_fixtures_compatible(item):
|
||||
fixturemanager = item.session._fixturemanager
|
||||
fixturedefs = []
|
||||
|
||||
if hasattr(item, "_request") and hasattr(item._request, "fixturenames"):
|
||||
for name in item._request.fixturenames:
|
||||
try:
|
||||
fixturedefs_pytest = fixturemanager.getfixturedefs(name, item)
|
||||
except AttributeError:
|
||||
fixturedefs_pytest = fixturemanager.getfixturedefs(name, item.nodeid)
|
||||
if fixturedefs_pytest:
|
||||
fixturedefs.extend(fixturedefs_pytest)
|
||||
|
||||
return fixturedefs
|
||||
|
||||
|
||||
if allure_pytest is not None:
|
||||
allure_pytest.listener._test_fixtures = _allure_test_fixtures_compatible
|
||||
57
joyhub_backend/test_case/TestCase/接口/test_hub_ops.py
Normal file
57
joyhub_backend/test_case/TestCase/接口/test_hub_ops.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import re
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
|
||||
from joyhub_backend.library.hubops_parser import load_hubops_cases
|
||||
from joyhub_backend.library.joyhub_interface import JoyhubInterface
|
||||
|
||||
|
||||
ALL_CASES = load_hubops_cases()
|
||||
|
||||
|
||||
def case_id(case):
|
||||
raw_id = "{}-{}".format(case.get("case_id"), case.get("name"))
|
||||
safe_id = re.sub(r"[^0-9A-Za-z_\u4e00-\u9fa5-]+", "_", raw_id)
|
||||
return safe_id[:120]
|
||||
|
||||
|
||||
@allure.feature("JoyHub Backend 接口自动化")
|
||||
class TestHubOps(object):
|
||||
test_case = JoyhubInterface()
|
||||
|
||||
def teardown_method(self):
|
||||
with allure.step("后置:记录用例结束日志"):
|
||||
logging.info("-----------------------------End-------------------------------")
|
||||
|
||||
@pytest.mark.parametrize("case", ALL_CASES, ids=case_id)
|
||||
def test_hub_ops_api(self, case):
|
||||
allure.dynamic.title("{} {}".format(case.get("case_id"), case.get("name")))
|
||||
allure.dynamic.story(case.get("name"))
|
||||
|
||||
with allure.step("前置:初始化接口客户端并准备鉴权 token"):
|
||||
headers = case.get("headers") or {}
|
||||
query = case.get("query") or {}
|
||||
body = case.get("body") or {}
|
||||
|
||||
response_data = self.test_case.request(
|
||||
case.get("name"),
|
||||
case.get("method"),
|
||||
case.get("url"),
|
||||
body=body,
|
||||
query=query,
|
||||
headers=headers,
|
||||
)
|
||||
with allure.step("断言内容:校验响应基础字段"):
|
||||
request_url = case.get("url") or ""
|
||||
full_url = request_url if request_url.startswith("http") else urljoin(self.test_case.base_url, request_url.lstrip("/"))
|
||||
if full_url.startswith(self.test_case.base_url):
|
||||
assert isinstance(response_data, dict), "断言失败原因:响应不是JSON对象,响应{}".format(response_data)
|
||||
assert "code" in response_data, "断言失败原因:响应缺少code字段,响应{}".format(response_data)
|
||||
assert response_data.get("msg") is not None, "断言失败原因:msg字段不能为空,响应{}".format(response_data)
|
||||
else:
|
||||
assert response_data is not None, "断言失败原因:非业务接口响应为空"
|
||||
logging.info("断言通过:%s", case.get("name"))
|
||||
0
joyhub_backend/test_case/__init__.py
Normal file
0
joyhub_backend/test_case/__init__.py
Normal file
170
joyhub_backend/test_case/run_tests.py
Normal file
170
joyhub_backend/test_case/run_tests.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
current_file_path = os.path.abspath(__file__)
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(current_file_path), '../../'))
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
TEST_CASE_DIR = 'joyhub_backend/test_case/TestCase'
|
||||
case_dir = os.path.join(project_root, TEST_CASE_DIR)
|
||||
ALLURE_RESULTS_DIR = os.path.join(project_root, 'joyhub_backend', 'test_case', 'reports', 'allure-results')
|
||||
ALLURE_REPORT_DIR = os.path.join(project_root, 'joyhub_backend', 'test_case', 'reports', 'allure-report')
|
||||
|
||||
|
||||
def ensure_dirs():
|
||||
os.makedirs(ALLURE_RESULTS_DIR, exist_ok=True)
|
||||
os.makedirs(ALLURE_REPORT_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def clean_allure_results():
|
||||
if os.path.exists(ALLURE_RESULTS_DIR):
|
||||
shutil.rmtree(ALLURE_RESULTS_DIR)
|
||||
os.makedirs(ALLURE_RESULTS_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def find_test_files(directory):
|
||||
test_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.py') and not file.startswith('__') and file != 'conftest.py':
|
||||
test_files.append(os.path.join(root, file))
|
||||
return test_files
|
||||
|
||||
|
||||
def run_pytest(args_list):
|
||||
env = os.environ.copy()
|
||||
env['PYTHONPATH'] = project_root + (os.pathsep + env['PYTHONPATH'] if 'PYTHONPATH' in env else '')
|
||||
cmd = ['python', '-m', 'pytest'] + args_list
|
||||
print("开始执行pytest...")
|
||||
print("执行命令: {}".format(' '.join('"{}"'.format(item) if ' ' in item else item for item in cmd)), flush=True)
|
||||
result = subprocess.run(cmd, cwd=project_root, env=env)
|
||||
print("pytest执行结束,退出码: {}".format(result.returncode), flush=True)
|
||||
return result.returncode
|
||||
|
||||
|
||||
def run_tests(target=None, test_type='all'):
|
||||
base_args = ['-v', '--tb=short', '--alluredir={}'.format(ALLURE_RESULTS_DIR)]
|
||||
if test_type == 'all':
|
||||
print("运行所有测试用例...")
|
||||
test_files = find_test_files(case_dir)
|
||||
if not test_files:
|
||||
print("错误: 未找到测试文件")
|
||||
return 1
|
||||
args = test_files + base_args
|
||||
elif test_type == 'dir':
|
||||
full_path = os.path.join(case_dir, target.replace('/', os.sep).replace('\\', os.sep))
|
||||
if not os.path.exists(full_path):
|
||||
print("错误: 目录不存在: {}".format(full_path))
|
||||
return 1
|
||||
print("按目录运行: {}".format(target))
|
||||
test_files = find_test_files(full_path)
|
||||
if not test_files:
|
||||
print("错误: 未找到测试文件")
|
||||
return 1
|
||||
args = test_files + base_args
|
||||
elif test_type == 'file':
|
||||
full_path = os.path.join(case_dir, target.replace('/', os.sep).replace('\\', os.sep))
|
||||
if not os.path.exists(full_path):
|
||||
print("错误: 文件不存在: {}".format(full_path))
|
||||
return 1
|
||||
print("按文件运行: {}".format(target))
|
||||
args = [full_path] + base_args
|
||||
elif test_type == 'keyword':
|
||||
print("按关键字运行: {}".format(target))
|
||||
test_files = find_test_files(case_dir)
|
||||
args = test_files + ['-k={}'.format(target)] + base_args
|
||||
elif test_type == 'marker':
|
||||
print("按pytest标记运行: {}".format(target))
|
||||
test_files = find_test_files(case_dir)
|
||||
args = test_files + ['-m={}'.format(target)] + base_args
|
||||
elif test_type == 'feature':
|
||||
print("按Allure feature运行: {}".format(target))
|
||||
test_files = find_test_files(case_dir)
|
||||
args = test_files + ['--allure-features={}'.format(target)] + base_args
|
||||
elif test_type == 'story':
|
||||
print("按Allure story运行: {}".format(target))
|
||||
test_files = find_test_files(case_dir)
|
||||
args = test_files + ['--allure-stories={}'.format(target)] + base_args
|
||||
else:
|
||||
print("错误: 未知的测试类型: {}".format(test_type))
|
||||
return 1
|
||||
return run_pytest(args)
|
||||
|
||||
|
||||
def generate_allure_report():
|
||||
print("开始生成Allure报告...", flush=True)
|
||||
if os.path.exists(ALLURE_REPORT_DIR):
|
||||
shutil.rmtree(ALLURE_REPORT_DIR)
|
||||
cmd = 'allure generate "{}" --output "{}"'.format(ALLURE_RESULTS_DIR, ALLURE_REPORT_DIR)
|
||||
print("执行命令: {}".format(cmd), flush=True)
|
||||
try:
|
||||
subprocess.run(cmd, check=True, shell=True)
|
||||
print("Allure报告生成成功: {}".format(ALLURE_REPORT_DIR))
|
||||
print("打开报告命令: allure open \"{}\"".format(ALLURE_REPORT_DIR))
|
||||
return 0
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, OSError) as error:
|
||||
print("生成Allure报告失败: {}".format(error))
|
||||
print("手动执行: {}".format(cmd))
|
||||
return 1
|
||||
|
||||
|
||||
def open_allure_report():
|
||||
cmd = 'allure open "{}"'.format(ALLURE_REPORT_DIR)
|
||||
try:
|
||||
subprocess.Popen(cmd, shell=True)
|
||||
print("Allure报告已打开: {}".format(ALLURE_REPORT_DIR))
|
||||
return 0
|
||||
except (FileNotFoundError, OSError) as error:
|
||||
print("打开Allure报告失败: {}".format(error))
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='JoyHub Backend 接口自动化测试执行工具')
|
||||
run_group = parser.add_mutually_exclusive_group(required=False)
|
||||
run_group.add_argument('--feature', type=str, help='按Allure feature运行')
|
||||
run_group.add_argument('--story', type=str, help='按Allure story运行')
|
||||
run_group.add_argument('--dir', type=str, help='按目录运行(相对于TestCase目录)')
|
||||
run_group.add_argument('--file', type=str, help='按文件运行(相对于TestCase目录)')
|
||||
run_group.add_argument('--keyword', type=str, help='按关键字运行')
|
||||
run_group.add_argument('--marker', type=str, help='按pytest标记运行')
|
||||
parser.add_argument('--report', action='store_true', help='生成Allure报告')
|
||||
parser.add_argument('--open', action='store_true', help='打开Allure报告')
|
||||
parser.add_argument('--no-report', action='store_true', help='不生成Allure报告')
|
||||
args = parser.parse_args()
|
||||
|
||||
ensure_dirs()
|
||||
clean_allure_results()
|
||||
if args.feature:
|
||||
exit_code = run_tests(args.feature, 'feature')
|
||||
elif args.story:
|
||||
exit_code = run_tests(args.story, 'story')
|
||||
elif args.dir:
|
||||
exit_code = run_tests(args.dir, 'dir')
|
||||
elif args.file:
|
||||
exit_code = run_tests(args.file, 'file')
|
||||
elif args.keyword:
|
||||
exit_code = run_tests(args.keyword, 'keyword')
|
||||
elif args.marker:
|
||||
exit_code = run_tests(args.marker, 'marker')
|
||||
else:
|
||||
exit_code = run_tests()
|
||||
|
||||
if args.report or not args.no_report:
|
||||
generate_allure_report()
|
||||
if args.open:
|
||||
open_allure_report()
|
||||
|
||||
print("=" * 80)
|
||||
print("测试执行完成" if exit_code == 0 else "测试执行失败,退出码: {}".format(exit_code))
|
||||
print("=" * 80)
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user