xmind2testcaseMax用例格式转换
xmind2testcaseMax工具代码
更新日志:20250521
基于xmind2testcase改造,新增对最新版本(25.01.01061)的支持;
一个步骤可支持多个预期结果,并可现实每个预期结果执行结果(失败、阻塞);
新增重要功能:将用例概要 转换为 前置条件,xmind看起来更直观;
新增对excle的支持;
自定义导出模板以适应公司meegle/jira导入;
优化web和cli代码,点点页面即可导出不同需求的用例文件;
文件导出内容的其他小优化;
1. 工具准备
1.1 安装 python
pass
1.2 安装 xmind2testcase
pass
1.3 安装 第三方包: openpyxl
pass
2.自定义导出模版+用例步骤支持多个预期结果
2.1 zentao.py 文件修改
python安装路径的**\Lib\site-packages\xmind2testcase文件夹下找到zentao.py**文件并打开。
自定义导出字段、新增导出excle文件、修改优先级、处理导出文件有空行问题、去除用例标题中的空格、处理用例步骤预期结果序号后多一个空格等自行参考 zentao.py 文件即可。
def xmind_to_zentao_csv_file(xmind_file, label='meegle', type='csv'):
"""Convert XMind file to a zentao csv file"""
xmind_file = get_absolute_path(xmind_file)
logging.info('Start converting XMind file(%s) to zentao file...', xmind_file)
testcases = get_xmind_testcase_list(xmind_file)
if label == 'meegle':
fileheader = ["用例名称", "模块", "Priority", "用例类型", "前置条件", "用例步骤", "预期结果"]
elif label == 'report':
fileheader = ["编号", "功能模块", "用例标题", "优先级", "前置条件", "步骤", "预期结果", "创建者", "编写人更新时间", "执行状态", "执行状态"]
else:
pass
# fileheader = ["用例标题", "模块", "优先级", "用例类型", "前置条件", "步骤", "预期结果"]
zentao_testcase_rows = [fileheader]
for testcase in testcases:
# print(f'testcase:{testcase}')
row = gen_a_testcase_row(testcase, label=label)
zentao_testcase_rows.append(row)
# Generate CSV file
zentao_csv_file = xmind_file[:-6] + '.csv'
if os.path.exists(zentao_csv_file):
os.remove(zentao_csv_file)
with open(zentao_csv_file, 'w', newline='', encoding='utf8') as f:
writer = csv.writer(f)
writer.writerows(zentao_testcase_rows)
logging.info('Convert XMind file(%s) to a zentao csv file(%s) successfully!', xmind_file, zentao_csv_file)
# Generate Excel file
zentao_excel_file = xmind_file[:-6] + '.xlsx'
if os.path.exists(zentao_excel_file):
os.remove(zentao_excel_file)
wb = Workbook()
ws = wb.active
ws.title = "Test Cases"
for row in zentao_testcase_rows:
ws.append(row)
wb.save(zentao_excel_file)
logging.info('Convert XMind file(%s) to a zentao excel file(%s) successfully!', xmind_file, zentao_excel_file)
if type == 'csv':
return zentao_csv_file
elif type == 'excel':
return zentao_excel_file
else:
raise ValueError("Unsupported file type. Please choose 'csv' or 'excel'.")
特别指出,以上方法,通过 label自由控制导出模板文件的标题;通过type控制导出文件类型(默认csv,可选excle),后面我们会改造web端type的值 以支持不同类型文件导出,效率更高。
2.3 parser.py 文件的修改
python安装路径的**\Lib\site-packages\xmind2testcase文件夹下找到parser.py**文件并打开,修改以下方法
2.3.1 支持:一个步骤多个预期结果
新增一个步骤支持多个预期结果(原有工具一个步骤只能一个预期结果);
步骤没有预期,默认填充’/’
def parse_a_test_step(step_dict):
test_step = TestStep()
test_step.actions = step_dict['title']
expected_topics = step_dict.get('topics', [])
if expected_topics: # 有预期结果
# 支持多个预期结果
for expected_topic in expected_topics:
# expected_topic = expected_topics[0]
# test_step.expectedresults = expected_topic['title'] # 一个测试步骤操作,一个测试预期结果
markers = expected_topic['markers']
result = get_test_result(markers)
# test_step.result = get_test_result(markers)
a = f"{expected_topic['title']}; "
if result:
a = f"{expected_topic['title']} - 【{result}】; "
test_step.expectedresults += a
# print(f'预期结果:{test_step.expectedresults}')
else: # 只有步骤无预期结果
# 默认填充'/'
test_step.expectedresults = '/'
markers = step_dict['markers']
test_step.result = get_test_result(markers)
logging.debug('finds a teststep: %s', test_step.to_dict())
return test_step
2.3.2 预期结果改造 (xmind执行用例可显示 每条预期执行情况:阻塞或失败)
def get_test_result(markers):
"""test result: non-execution:0, pass:1, failed:2, blocked:3, skipped:4"""
if isinstance(markers, list): # 完成符号 喜欢
if 'symbol-right' in markers or 'c_simbol-right' in markers or 'task-done' in markers or 'c_symbol_like' in markers: # 通过
result = '通过' # 不喜欢
elif 'symbol-wrong' in markers or 'c_simbol-wrong' in markers or 'c_symbol_dislike' in markers: # 失败
result = '失败' # 红色感叹号
elif 'symbol-pause' in markers or 'c_simbol-pause' in markers or 'symbol-exclam' in markers: # 阻塞
result = '阻塞'
# elif 'symbol-minus' in markers or 'c_simbol-minus' in markers: # 跳过
# result = '跳过'
else:
result = ''
else:
result = ''
return result

2.3.1 去除用例标题中的空格
def gen_testcase_title(topics):
"""Link all topic's title as testcase title"""
titles = [topic['title'] for topic in topics]
titles = filter_empty_or_ignore_element(titles)
# when separator is not blank, will add space around separator, e.g. '/' will be changed to ' / '
#下面模块和功能是用"空格"分割,如用"/"分隔,则替换为:separator = '/'
#separator = '/'
separator = config['sep']
if separator != ' ':
# 修改前
#separator = ' {} '.format(separator)
# 修改后
separator = f'{separator}'
return separator.join(titles)
效果图
2.4 支持新版xmind
安装
xmindparser库(目前我的版本是:v1.0.9)python安装路径的**\Lib\site-packages\xmind2testcase文件夹下找到utils.py**文件并打开,修改以下方法
from xmindparser import xmind_to_dict,is_xmind_zen def get_xmind_testsuites(xmind_file): """Load the XMind file and parse to `xmind2testcase.metadata.TestSuite` list""" xmind_file = get_absolute_path(xmind_file) # 新增 判断是否为新版xmind(zen及其之后的版本) if is_xmind_zen(xmind_file): print("yes,新版xmind") xmind_content_dict = xmind_to_dict(xmind_file) else: workbook = xmind.load(xmind_file) xmind_content_dict = workbook.getData()
python安装路径的**\Lib\site-packages\xmindparser文件夹下找到zenreader.py**文件并打开,新增以下方法
def update_notes_with_summary(topics): """ 递归遍历主题结构,将 summary 内容合并到 notes.plain.content 中 """ if isinstance(topics, list): for topic in topics: update_notes_with_summary(topic) elif isinstance(topics, dict): # 处理当前主题的 summary if 'summary' in topics: summary_content = "\n".join([summary['title'] for summary in topics['summary']]) if 'notes' in topics: if 'plain' in topics['notes']: topics['notes']['plain']['content'] += "\n" + summary_content else: topics['notes']['plain'] = {'content': summary_content} else: topics['notes'] = {'plain': {'content': summary_content}} # 处理 summaries if 'summaries' in topics and 'children' in topics and 'attached' in topics['children']: for summary_info in topics['summaries']: range_str = summary_info['range'] topic_id = summary_info['topicId'] # 解析 range_str 为范围 if ',' in range_str: start, end = map(int, range_str.strip('()').split(',')) else: start = int(range_str.strip('()')) end = start + 1 # 找到对应的 summary summary_title = None if 'summary' in topics['children']: for summary in topics['children']['summary']: if summary['id'] == topic_id: summary_title = summary['title'] break if summary_title: for i in range(start, end+1): if i < len(topics['children']['attached']): attached_topic = topics['children']['attached'][i] if 'notes' in attached_topic: if 'plain' in attached_topic['notes']: attached_topic['notes']['plain']['content'] += "\n" + summary_title else: attached_topic['notes']['plain'] = {'content': summary_title} else: attached_topic['notes'] = {'plain': {'content': summary_title}} # 递归处理子主题 if 'children' in topics: if 'attached' in topics['children']: for child in topics['children']['attached']: update_notes_with_summary(child) # 特别处理 rootTopic if 'rootTopic' in topics: update_notes_with_summary(topics['rootTopic'])
修改
sheet_to_dict方法def sheet_to_dict(sheet): """convert a sheet to dict type.""" # ---------- 新增开始 --------- print(f"sheet:{sheet}") if sheet: # 将sheet测试用例的概要信息变为前置条件 update_notes_with_summary(sheet) # ---------- 新增结束 --------- topic = sheet['rootTopic'] result = {'title': sheet['title'], 'topic': node_to_dict(topic), 'structure': get_sheet_structure(sheet)}
CLI命令配置


elif len(sys.argv) == 3 and sys.argv[2] == '-excel2meegle':
kpay_excel2meegle_file = xmind_to_zentao_csv_file(xmind_file, label='meegle', type='excel')
logging.info('Convert XMind file to excel2meegle excel file successfully: %s', kpay_excel2meegle_file)
elif len(sys.argv) == 3 and sys.argv[2] == '-excel2report':
kpay_excel2report_file = xmind_to_zentao_csv_file(xmind_file, label='report', type='excel')
logging.info('Convert XMind file to kpay excel2report file successfully: %s', kpay_excel2report_file)
转化命令
xmind2testcase /path/to/testcase.xmind -excel2meegle
xmind2testcase /path/to/testcase.xmind -excel2report
xmind2testcase /path/to/testcase.xmind -json
Web配置
效果

web前端应用:新增路由
python安装路径的**\Lib\site-packages\webtool文件夹下找到application.py**文件并打开,新增以下方法
@app.route('/<filename>/to/meegle_excel') def download_kpay_xlsx_file_by_pandas_to_meegle(filename): full_path = join(app.config['UPLOAD_FOLDER'], filename) if not exists(full_path): abort(404) kpay_xlsx_file = xmind_to_zentao_csv_file(full_path, label='meegle', type='excel') filename = os.path.basename(kpay_xlsx_file) if kpay_xlsx_file else abort(404) return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True) @app.route('/<filename>/to/report_excel') def download_kpay_xlsx_file_by_pandas_to_report(filename): full_path = join(app.config['UPLOAD_FOLDER'], filename) if not exists(full_path): abort(404) kpay_xlsx_file = xmind_to_zentao_csv_file(full_path, label='report', type='excel') filename = os.path.basename(kpay_xlsx_file) if kpay_xlsx_file else abort(404) return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True)

导出文件-首页入口标签 Python 安装路径的**\Lib\site-packages\webtool\templates文件夹下找到index. Html**文件并修改

<td><a href="{{ url_for('uploaded_file',filename=record[1]) }}">XMIND</a> | <a href="{{ url_for('download_zentao_file',filename=record[1]) }}">CSV</a> | <a href="{{ url_for('download_kpay_xlsx_file_by_pandas_to_meegle',filename=record[1]) }}">Excel-meegle</a> | <a href="{{ url_for('download_kpay_xlsx_file_by_pandas_to_report',filename=record[1]) }}">Excel-report</a> | <a href="{{ url_for('download_testlink_file',filename=record[1]) }}">XML</a> | <a href="{{ url_for('preview_file',filename=record[1]) }}">PREVIEW</a> | <a href="{{ url_for('delete_file',filename=record[1], record_id=record[4]) }}">DELETE</a> </td>
导出文件-预览页入口标签 Python 安装路径的**\Lib\site-packages\webtool\templates文件夹下找到preview. Html**文件并修改

<h2> TestSuites: {{ suite_count }} / TestCases: {{ suite | length }} / <a href="{{ url_for('download_zentao_file', filename=name) }}">Get Zentao CSV</a> / <a href="{{ url_for('download_kpay_xlsx_file_by_pandas_to_meegle',filename= name) }}">Get KPay Excel-meegle</a> / <a href="{{ url_for('download_kpay_xlsx_file_by_pandas_to_report',filename= name) }}">Get KPay Excel-report</a> / <a href="{{ url_for('download_testlink_file', filename=name) }}">Get TestLink XML</a> / <a href="{{ url_for('index') }}">Go Back</a> </h2>
完整版二开文件
下载链接: https://xuefeng365.lanzouo.com/b00l1k6rpc 密码:axiv