v0.2.0
This commit is contained in:
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.13.11
|
||||||
38
cctools.spec
Normal file
38
cctools.spec
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[('E:\\pycharmProjects\\CCTools\\.venv\\Lib\\site-packages\\nicegui', 'nicegui')],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='cctools',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
20
custom_components/log.py
Normal file
20
custom_components/log.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from nicegui import ui
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
class MyLog(ui.log):
|
||||||
|
def __init__(self, max_lines: t.Optional[int] = None) -> None:
|
||||||
|
super().__init__(max_lines)
|
||||||
|
|
||||||
|
def debug_emit(self, text: str):
|
||||||
|
self.push(text, classes = "text-grey")
|
||||||
|
|
||||||
|
def info_emit(self, text: str):
|
||||||
|
self.push(text, classes = "text-blue")
|
||||||
|
|
||||||
|
def warning_emit(self, text: str):
|
||||||
|
self.push(text, classes = "text-orange")
|
||||||
|
|
||||||
|
def error_emit(self, text: str):
|
||||||
|
self.push(text, classes = "text-red")
|
||||||
|
|
||||||
|
__all__ = ['MyLog']
|
||||||
15
datas/global_data.py
Normal file
15
datas/global_data.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from nicegui import binding
|
||||||
|
|
||||||
|
class GlobalDatas:
|
||||||
|
user_select_path = binding.BindableProperty()
|
||||||
|
is_loading = binding.BindableProperty()
|
||||||
|
progress_value = binding.BindableProperty()
|
||||||
|
|
||||||
|
def __init__(self, inital_path):
|
||||||
|
self.user_select_path = inital_path
|
||||||
|
self.is_loading = False
|
||||||
|
self.progress_value = 0.0
|
||||||
|
|
||||||
|
global_data = GlobalDatas("还未选择路径")
|
||||||
|
|
||||||
|
__all__ = ["global_data"]
|
||||||
46
main.py
Normal file
46
main.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from nicegui import ui
|
||||||
|
from custom_components.log import MyLog
|
||||||
|
from datas.global_data import global_data
|
||||||
|
from main_events import handle_click, handle_cpu_transform
|
||||||
|
|
||||||
|
# 统一设置按钮为方形
|
||||||
|
ui.button.default_props('square')
|
||||||
|
|
||||||
|
@ui.refreshable
|
||||||
|
def user_path():
|
||||||
|
path_input = ui.input().props('square outlined dense disable').classes("flex-grow min-w-0")
|
||||||
|
path_input.bind_value_from(global_data, 'user_select_path')
|
||||||
|
|
||||||
|
def root():
|
||||||
|
with ui.column().classes('w-full h-full flex flex-col'):
|
||||||
|
with ui.card().classes('w-full flex'):
|
||||||
|
with ui.row().classes('justify-center items-center w-full'):
|
||||||
|
ui.button("请选择文件", on_click = handle_click)
|
||||||
|
ui.label("当前选择文件路径:")
|
||||||
|
user_path()
|
||||||
|
|
||||||
|
# 上半部分
|
||||||
|
with ui.column().classes('w-full flex-grow overflow-auto min-h-[350px]'):
|
||||||
|
with ui.tabs().props("dense align='left'").classes('w-full').classes("bg-teal text-yellow shadow-2") as tabs:
|
||||||
|
tab_home = ui.tab('大纲转说明')
|
||||||
|
tab_profile = ui.tab('其他小工具')
|
||||||
|
tab_settings = ui.tab('设置')
|
||||||
|
|
||||||
|
with ui.tab_panels(tabs, value = tab_home).classes('w-full flex-grow'):
|
||||||
|
with ui.tab_panel(tab_home):
|
||||||
|
ui.button(
|
||||||
|
"CPU-大纲转说明(测试项描述分点)", icon = "swap_horiz", on_click = lambda e: handle_cpu_transform(log = log_element)
|
||||||
|
)
|
||||||
|
|
||||||
|
with ui.tab_panel(tab_profile):
|
||||||
|
ui.label('欢迎来到其他小工具')
|
||||||
|
|
||||||
|
with ui.tab_panel(tab_settings):
|
||||||
|
ui.label("欢迎来到设置界面")
|
||||||
|
|
||||||
|
ui.linear_progress(color = 'info', show_value = False, size = '30px').bind_value_from(global_data,
|
||||||
|
'progress_value').props('stripe')
|
||||||
|
log_element = MyLog().classes('flex-grow overflow-auto min-h-[270px] select-text').style("white-space:pre-wrap;")
|
||||||
|
log_element.debug_emit("界面初始化完成...")
|
||||||
|
|
||||||
|
ui.run(root, language = 'zh-CN', native = True, window_size = (1200, 900), reload = False)
|
||||||
29
main_events.py
Normal file
29
main_events.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from nicegui import app, ui
|
||||||
|
from datas.global_data import global_data
|
||||||
|
import asyncio
|
||||||
|
from stuctor.ope import Operator
|
||||||
|
|
||||||
|
# 选择文件点击
|
||||||
|
async def handle_click():
|
||||||
|
file = await app.native.main_window.create_file_dialog( # pyright: ignore[reportOptionalMemberAccess]
|
||||||
|
file_types = ('DOCX Files (*.docx;*.docx;*.docx)', )
|
||||||
|
)
|
||||||
|
if file is not None:
|
||||||
|
global_data.user_select_path = file[0]
|
||||||
|
|
||||||
|
# cpu-大纲转说明
|
||||||
|
async def handle_cpu_transform(log):
|
||||||
|
if not global_data.user_select_path or global_data.user_select_path == '还未选择路径':
|
||||||
|
ui.notify("请选择文件后重试", type = 'warning')
|
||||||
|
else:
|
||||||
|
global_data.progress_value = 0.1
|
||||||
|
global_data.is_loading = True
|
||||||
|
log.push("开始转换文档...")
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
operation = Operator(str(global_data.user_select_path), "测试说明", "测试记录", log)
|
||||||
|
operation.main_parse('CPU')
|
||||||
|
operation.render()
|
||||||
|
global_data.progress_value = 1.0
|
||||||
|
log.push("转换完毕...")
|
||||||
|
ui.notify('转换完成,请查看软件根目录输出docx', type = 'positive')
|
||||||
|
global_data.is_loading = False
|
||||||
38
myapp.spec
Normal file
38
myapp.spec
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[('E:\\pycharmProjects\\CCTools\\.venv\\Lib\\site-packages\\nicegui', 'nicegui')],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='myapp',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
BIN
origin_document/xcy40truk4h.docx
Normal file
BIN
origin_document/xcy40truk4h.docx
Normal file
Binary file not shown.
14
pyproject.toml
Normal file
14
pyproject.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[project]
|
||||||
|
name = "cctools"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13.11"
|
||||||
|
dependencies = [
|
||||||
|
"auto-py-to-exe>=2.48.1",
|
||||||
|
"docxtpl>=0.20.2",
|
||||||
|
"nicegui==3.6.1",
|
||||||
|
"pyinstaller>=6.18.0",
|
||||||
|
"python-docx>=1.2.0",
|
||||||
|
"pywebview>=6.1",
|
||||||
|
]
|
||||||
2
stuctor/constants.py
Normal file
2
stuctor/constants.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# xml寻找节点的命名空间前缀
|
||||||
|
PREFIX = r"{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
|
||||||
43
stuctor/cpu_static.py
Normal file
43
stuctor/cpu_static.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# cpu文档审查固定格式
|
||||||
|
wdsc_content_list = [
|
||||||
|
{
|
||||||
|
'content': '测试人员人工阅读文档,依据文档检查单对软件文档进行审查,审查软件文档内容是否完整',
|
||||||
|
'yuqi': '软件文档内容完整'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'content': '审查软件文档描述是否准确',
|
||||||
|
'yuqi': "软件文档描述准确"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'content': '审查软件文档格式是否规范',
|
||||||
|
'yuqi': "软件文档格式规范"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'content': '审查软件文档是否文文一致',
|
||||||
|
'yuqi': "软件文档文文一致"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
jtfx_content_list = [{
|
||||||
|
'content':
|
||||||
|
'使用静态分析工具统计软件质量度量信息',
|
||||||
|
'yuqi':
|
||||||
|
'1)软件总注释率不小于20%(注释行数/软件规模*100%)\a2)模块的平均规模不大于200行(模块代码行数之和/模块数)\a3) 模块的平均圈复杂度不大于10(模块圈复杂度之和/模块总数)\a4)模块的平均扇出数不大于7(模块扇出数之和/模块总数)'
|
||||||
|
}, {
|
||||||
|
'content': '使用静态分析工具结合人工分析对控制流和数据流进行分析',
|
||||||
|
'yuqi': '软件满足控制流和数据流要求'
|
||||||
|
}]
|
||||||
|
|
||||||
|
dmsc_content_list = [{
|
||||||
|
'content': '依据编程准则的要求,对程序的编码与编程准则进行符合性检查',
|
||||||
|
'yuqi': '代码编码符合编程准则'
|
||||||
|
}, {
|
||||||
|
'content': '审查程序代码的条件判别、控制流程、数据处理等满足设计要求',
|
||||||
|
'yuqi': '代码流程实现正确'
|
||||||
|
}, {
|
||||||
|
'content': '依据设计文档,审查程序代码的结构设计的合理性,包括程序结构设计和数据结构设计',
|
||||||
|
'yuqi': '代码结构设计合理'
|
||||||
|
}, {
|
||||||
|
'content': '依据需求文档及其他相关资料,审查程序代码的需求层的功能实现是否正确',
|
||||||
|
'yuqi': '代码实现需求正确'
|
||||||
|
}]
|
||||||
19
stuctor/dmzc.py
Normal file
19
stuctor/dmzc.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"""FPGA-代码走查、代码审查"""
|
||||||
|
|
||||||
|
def parse_dmsc(desc, content):
|
||||||
|
return [{
|
||||||
|
'content': '通过人工审查及借助Vlint工具辅助分析的方式对代码审查范围内的源代码开展多个方面的审查,包括编程准则检查、代码流程审查、软件结构审查、需求实现审查、工程一致性检查',
|
||||||
|
'yuqi': '代码编码符合编程准则、代码流程实现正确、代码结构设计合理,代码实现需求正确'
|
||||||
|
}]
|
||||||
|
|
||||||
|
def parse_dmzc(desc,content):
|
||||||
|
return [{
|
||||||
|
'content': '设计具有代表性的测试用例,明确输入条件、走查步骤和执行结果,成立走查小组,集体对代码走查范围内的程序代码进行走查,记录走查结果',
|
||||||
|
'yuqi': '按照设计的用例完成走查,走查结果符合预期'
|
||||||
|
}]
|
||||||
|
|
||||||
|
def parse_luoji(desc,content):
|
||||||
|
return [{
|
||||||
|
'content': '使用仿真工具QuestaSim对测试覆盖率进行统计,包括语句覆盖率统计、分支覆盖率统计、状态机覆盖统计、条件覆盖统计、表达式覆盖统计',
|
||||||
|
'yuqi': '各测试步骤、测试用例执行结果与预期一致,功能实现正确'
|
||||||
|
}]
|
||||||
283
stuctor/item.py
Normal file
283
stuctor/item.py
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
from abc import ABC
|
||||||
|
from typing import Union, List
|
||||||
|
from docx.table import Table
|
||||||
|
from stuctor.patterns import ParsePatterns
|
||||||
|
from stuctor.wdsc import parse_wdsc
|
||||||
|
from stuctor.dmzc import parse_dmsc, parse_dmzc, parse_luoji
|
||||||
|
from stuctor.tools import normal_parse_content
|
||||||
|
from stuctor.cpu_static import wdsc_content_list, jtfx_content_list, dmsc_content_list
|
||||||
|
from datas.global_data import global_data
|
||||||
|
from nicegui import ui
|
||||||
|
|
||||||
|
class Item(ABC):
|
||||||
|
def __init__(self, type: str) -> None:
|
||||||
|
self.type: str = type
|
||||||
|
self.capter: str = "空"
|
||||||
|
|
||||||
|
class TitleItem(Item):
|
||||||
|
def __init__(self, rank: str, title: str) -> None:
|
||||||
|
self.title = title
|
||||||
|
super().__init__(rank)
|
||||||
|
|
||||||
|
class TableItemList(Item):
|
||||||
|
"""Table项"""
|
||||||
|
def __init__(
|
||||||
|
self, table: Table, current_title_item: Union[TitleItem, None], logger: ui.log, parse_content_strategy: str
|
||||||
|
) -> None:
|
||||||
|
self.logger = logger
|
||||||
|
# 测试项名称
|
||||||
|
self.name = table.cell(0, 1).text
|
||||||
|
# 2.测试项标识
|
||||||
|
self.ident = table.cell(0, 3).text
|
||||||
|
# 3.追踪关系
|
||||||
|
self.zhui = self.__parse_zhui_whole(current_title_item)
|
||||||
|
# 4.table_item列表
|
||||||
|
self.table_item: list[TableItem] = []
|
||||||
|
# ~~~~~
|
||||||
|
# 5.储存分割用例子项标题
|
||||||
|
self.case_index_list = []
|
||||||
|
# 6.储存分割的综述
|
||||||
|
self.case_zongsu_list = []
|
||||||
|
if table.cell(4, 0).text == '测试方法':
|
||||||
|
if parse_content_strategy == 'FPGA':
|
||||||
|
self.__parse_content_fpga(table.cell(3, 2).text, table.cell(4, 2).text)
|
||||||
|
elif parse_content_strategy == 'CPU':
|
||||||
|
self.__parse_content_cpu(table.cell(3, 2).text, table.cell(4, 2).text)
|
||||||
|
super().__init__("table")
|
||||||
|
|
||||||
|
# 解析追踪关系一栏
|
||||||
|
def __parse_zhui_whole(self, last_title_item: Union[TitleItem, None]):
|
||||||
|
if last_title_item:
|
||||||
|
return ['软件测试依据:测评大纲', f'测评大纲:({last_title_item.capter}) {last_title_item.title}', f'测评大纲标识:{self.ident}']
|
||||||
|
|
||||||
|
# 填充用例名称和综述的index
|
||||||
|
def __parse_regex(
|
||||||
|
self,
|
||||||
|
desc_paras: List[str],
|
||||||
|
paras: List[str],
|
||||||
|
):
|
||||||
|
for index, val in enumerate(desc_paras):
|
||||||
|
if len(ParsePatterns.step_pattern.findall(val)) > 0:
|
||||||
|
self.case_zongsu_list.append(index)
|
||||||
|
for idx, value in enumerate(paras):
|
||||||
|
if len(ParsePatterns.step_pattern.findall(value)) > 0:
|
||||||
|
self.case_index_list.append(idx)
|
||||||
|
|
||||||
|
def __obtain_index(self, description, content):
|
||||||
|
paras = content.replace(" ", "").split('\n')
|
||||||
|
desc_paras = description.split('\n')
|
||||||
|
self.__parse_regex(desc_paras, paras)
|
||||||
|
return paras, desc_paras
|
||||||
|
|
||||||
|
def __parse_content_cpu(self, description: str, content: str):
|
||||||
|
paras, desc_paras = self.__obtain_index(description, content)
|
||||||
|
if '文档审查' in paras[0]:
|
||||||
|
content_list = wdsc_content_list
|
||||||
|
zongsu = "\a".join(desc_paras[1:])
|
||||||
|
csh = '文档审查单齐备,研制方已提交文档'
|
||||||
|
self.table_item.append(TableItem('文档审查', content_list, 0, self.ident, zongsu, csh, self.logger, has_r1 = False))
|
||||||
|
elif '静态分析' in paras[0]:
|
||||||
|
content_list = jtfx_content_list
|
||||||
|
zongsu = desc_paras[1]
|
||||||
|
csh = '研发方已提供源代码'
|
||||||
|
self.table_item.append(TableItem('静态分析', content_list, 0, self.ident, zongsu, csh, self.logger, has_r1 = False))
|
||||||
|
elif '代码审查' in paras[0]:
|
||||||
|
zongsu = desc_paras[1]
|
||||||
|
csh = '研发方已提供源代码'
|
||||||
|
self.table_item.append(TableItem('静态分析', dmsc_content_list, 0, self.ident, zongsu, csh, self.logger, has_r1 = False))
|
||||||
|
else:
|
||||||
|
cIdx = 0 # 用例计数
|
||||||
|
for index in range(len(self.case_index_list)):
|
||||||
|
title = paras[self.case_index_list[index]] # 1.全体:获取标题
|
||||||
|
# 2.获取综述
|
||||||
|
if 'XQ' in "".join(desc_paras):
|
||||||
|
try:
|
||||||
|
zongsu = desc_paras[self.case_zongsu_list[index] + 1]
|
||||||
|
except IndexError:
|
||||||
|
self.logger.push(f"注意用例{title}-{self.ident},测试方法没有对应的测试项描述,该用例转换失败...")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
zongsu = desc_paras[0]
|
||||||
|
# 3.初始化,后续可由用户填写
|
||||||
|
csh = '具备测试环境,测试工具已就绪'
|
||||||
|
try:
|
||||||
|
content_list = self.__parse_step_cpu(paras[self.case_index_list[index] + 1:self.case_index_list[index + 1]])
|
||||||
|
except IndexError:
|
||||||
|
content_list = self.__parse_step_cpu(paras[self.case_index_list[index] + 1:])
|
||||||
|
self.table_item.append(
|
||||||
|
TableItem(
|
||||||
|
title.split("(")[0].strip("123456789)),.,。;;::"),
|
||||||
|
content_list,
|
||||||
|
cIdx,
|
||||||
|
self.ident,
|
||||||
|
zongsu,
|
||||||
|
csh,
|
||||||
|
self.logger,
|
||||||
|
has_r1 = False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cIdx += 1
|
||||||
|
|
||||||
|
# 分解测试类型表格中一大堆东西给每个TableItem初始化 - FPGA大纲转说明
|
||||||
|
def __parse_content_fpga(self, description: str, content: str):
|
||||||
|
# 1.文档审查单独解析
|
||||||
|
if '文档审查' in description:
|
||||||
|
res = parse_wdsc(content)
|
||||||
|
zongsu = '对开发方提交的文档进行审查'
|
||||||
|
csh = '文档审查单齐备'
|
||||||
|
item = TableItem('文档审查', res, 0, self.ident, zongsu, csh, self.logger)
|
||||||
|
self.table_item.append(item)
|
||||||
|
elif '代码审查' in description:
|
||||||
|
res = parse_dmsc(description, content)
|
||||||
|
zongsu = '对开发方提交的工程、代码进行审查'
|
||||||
|
csh = '代码审查单齐备'
|
||||||
|
item = TableItem('代码审查', res, 0, self.ident, zongsu, csh, self.logger)
|
||||||
|
self.table_item.append(item)
|
||||||
|
elif '代码走查' in description:
|
||||||
|
res = parse_dmzc(description, content)
|
||||||
|
zongsu = '对被测软件全部代码和关键部分进行代码走查'
|
||||||
|
csh = '代码走查范围已确定,范围内代码和模块具备走查条件'
|
||||||
|
item = TableItem('代码走查', res, 0, self.ident, zongsu, csh, self.logger)
|
||||||
|
self.table_item.append(item)
|
||||||
|
elif '逻辑测试' in description:
|
||||||
|
res = parse_luoji(description, content)
|
||||||
|
zongsu = '源码动态测试语句、分支、状态机、条件、表达式覆盖率均需达到100%。对覆盖率达不到要求的软件,应对未覆盖的部分逐一进行分析和确认,并提供分析描述'
|
||||||
|
csh = '软件已准备就绪'
|
||||||
|
item = TableItem('逻辑测试', res, 0, self.ident, zongsu, csh, self.logger)
|
||||||
|
self.table_item.append(item)
|
||||||
|
else:
|
||||||
|
paras, desc_paras = self.__obtain_index(description, content)
|
||||||
|
# 在判断之前取出用例的title和cIdx
|
||||||
|
cIdx = 0
|
||||||
|
for index in range(len(self.case_index_list)):
|
||||||
|
title = paras[self.case_index_list[index]] # 1.全体:获取标题
|
||||||
|
# 2.获取当前用例的综述
|
||||||
|
try:
|
||||||
|
zongsu = desc_paras[self.case_zongsu_list[index] + 1]
|
||||||
|
except IndexError:
|
||||||
|
self.logger.push(f"注意用例{title}-{self.ident},测试方法没有对应的测试项描述,该用例转换失败...")
|
||||||
|
continue
|
||||||
|
# 3.初始化,后续可由用户填写
|
||||||
|
csh = '具备测试环境'
|
||||||
|
# 4.分情况处理content_list了
|
||||||
|
try:
|
||||||
|
content_list = self.__parse_step_fpga(paras[self.case_index_list[index] + 1:self.case_index_list[index + 1]])
|
||||||
|
except IndexError:
|
||||||
|
content_list = self.__parse_step_fpga(paras[self.case_index_list[index] + 1:])
|
||||||
|
self.table_item.append(TableItem(title.split("(")[0], content_list, cIdx, self.ident, zongsu, csh, self.logger))
|
||||||
|
# 最后用例计数+1
|
||||||
|
cIdx += 1
|
||||||
|
|
||||||
|
# 重点函数:首先分实物或非实物 - fpga大纲转说明
|
||||||
|
def __parse_step_fpga(self, paras: List[str]):
|
||||||
|
# 返回格式为:[{'content':'步骤内容','yuqi':'预期'},{'content':'步骤内容','yuqi':'预期'}]
|
||||||
|
# 1.大类仿真测试环境
|
||||||
|
content_list = []
|
||||||
|
if '仿真测试' in paras[0] or '功能仿真' in paras[0]:
|
||||||
|
duo_one_hang = 0
|
||||||
|
for i, hang in enumerate(paras):
|
||||||
|
if '通过仿真' in hang:
|
||||||
|
duo_one_hang = i
|
||||||
|
break
|
||||||
|
# 变量:“通过仿真”那行str
|
||||||
|
flag_para = paras[duo_one_hang]
|
||||||
|
flag_para_rear = flag_para.split('通过仿真')[-1]
|
||||||
|
# 1.2.多对一情况 - 看“通过仿真”在第几行,且后续没有行
|
||||||
|
if duo_one_hang > 1 and duo_one_hang == len(paras) - 1:
|
||||||
|
prefix = paras[0]
|
||||||
|
for pa in paras[1:duo_one_hang]:
|
||||||
|
content_list.append({
|
||||||
|
'content': (prefix + pa).strip(";;.。,, "),
|
||||||
|
'yuqi': '通过仿真' + flag_para_rear.strip(";;.。,, ")
|
||||||
|
})
|
||||||
|
# 1.3.一对多情况 - 看“通过仿真”如果在第一行且后续有多行
|
||||||
|
elif duo_one_hang == 0 and len(paras) - 1 > duo_one_hang:
|
||||||
|
# 1.3.1.如果有2段,第一段是仿真,第二段是实物测试 - 判断第二段是否是实物测试
|
||||||
|
if '实物测试' in paras[1]:
|
||||||
|
for pa in paras:
|
||||||
|
# 每一行就是一个步骤,“查看”分割
|
||||||
|
content_list.append({'content': pa.split('查看')[0] + '查看', 'yuqi': pa.split('查看')[1].strip(";;.。,, ")})
|
||||||
|
# 1.3.1 特殊,工况处理
|
||||||
|
elif '功耗分析' in paras[0]:
|
||||||
|
ft = '仿真测试环境下,使用设计检查的方法,在开发环境中设置以下工况,通过功耗分析报告查看功耗情况'
|
||||||
|
for pa in paras[1:]:
|
||||||
|
content_list.append({'content': ft.replace('以下工况', pa.strip(";;.。,, ")), 'yuqi': '功耗报告中功耗情况符合要求'})
|
||||||
|
else:
|
||||||
|
content = paras[0].split('通过仿真')[0]
|
||||||
|
hou_prefix = paras[0].split('通过仿真')[-1]
|
||||||
|
for pa in paras[1:len(paras)]:
|
||||||
|
content_list.append({'content': content.strip(";;.。,, "), 'yuqi': (hou_prefix + pa).strip(";;.。,, ")})
|
||||||
|
# 1.4.多对多情况 - 看“通过仿真”如果在第一行且后续有多行
|
||||||
|
elif duo_one_hang > 1 and len(paras) - 1 > duo_one_hang:
|
||||||
|
prefix = paras[0]
|
||||||
|
for pa in paras[1:duo_one_hang]:
|
||||||
|
content_list.append({
|
||||||
|
'content': (prefix + pa).strip(";;.。,, "),
|
||||||
|
'yuqi': '通过仿真' + ("".join(paras[duo_one_hang:]).strip(";;.。,,:: "))
|
||||||
|
})
|
||||||
|
# 1.1.一对一情况 - 简单通过“通过仿真”字样分割
|
||||||
|
else:
|
||||||
|
|
||||||
|
# 1.X.1.第一个配置项特殊处理器边界,第二个配置项正常
|
||||||
|
if '边界测试' in paras[0]:
|
||||||
|
self.logger.push("注意程序对边界测试的步骤拆分可能有错误,请检查!!!")
|
||||||
|
for pa in paras[1:]:
|
||||||
|
front = pa.split('查看')[0].strip(";;.。,,:: ") + '查看'
|
||||||
|
rear = pa.split('查看')[1].strip(";;.。,,:: ")
|
||||||
|
content_list.append({'content': front, 'yuqi': rear})
|
||||||
|
|
||||||
|
else:
|
||||||
|
content_list = normal_parse_content(paras)
|
||||||
|
|
||||||
|
# 2.大类实物测试环境
|
||||||
|
elif '实物测试' in paras[0]:
|
||||||
|
# 2.2.如果实物测试多行,则第一行为前缀,后面每行一个步骤
|
||||||
|
if len(paras) > 1:
|
||||||
|
pre = paras[0].strip(";;.。,,:: ")
|
||||||
|
for paragraph in paras[1:]:
|
||||||
|
front = paragraph.split('查看')[0].strip(";;.。,,:: ")
|
||||||
|
rear = paragraph.split('查看')[-1].strip(";;.。,,:: ")
|
||||||
|
content_list.append({'content': pre + front, 'yuqi': rear})
|
||||||
|
# 2.1.一对一情况 - 通过“通过”字样分割
|
||||||
|
else:
|
||||||
|
content_list = normal_parse_content(paras, '查看')
|
||||||
|
|
||||||
|
# 3.大类设计检查方法 - 直接遍历每行,每行一个步骤
|
||||||
|
elif '设计检查' in paras[0] or '静态时序' in paras[0]:
|
||||||
|
for para in paras:
|
||||||
|
rear = para.split('查看')[-1].replace("是否", "").strip(";;.。,,:: ")
|
||||||
|
content_list.append({'content': para.strip(";;.。,,:: "), 'yuqi': rear})
|
||||||
|
elif '时序仿真' in paras[0]:
|
||||||
|
content_list = normal_parse_content(paras, '查看')
|
||||||
|
else:
|
||||||
|
print(f"{self.ident},{self.name}###测试项写法有错,请修改后再转换...")
|
||||||
|
return content_list
|
||||||
|
|
||||||
|
# 重点函数 - cpu大纲转说明
|
||||||
|
def __parse_step_cpu(self, paras: List[str]):
|
||||||
|
content_list = []
|
||||||
|
for para in paras:
|
||||||
|
content_list.append({'content': para.strip("123456789)),.,。;;::"), 'yuqi': '各测试步骤、测试用例执行结果与预期一致,功能实现正确'})
|
||||||
|
return content_list
|
||||||
|
|
||||||
|
class TableItem:
|
||||||
|
def __init__(self, title: str, content_list, cIdx, father_ident, zongsu, csh, logger: ui.log, has_r1 = True) -> None:
|
||||||
|
# 属性 - name用例名称
|
||||||
|
self.name = title
|
||||||
|
global_data.progress_value += 0.01 # 耦合
|
||||||
|
# 输出日志记录和进度条
|
||||||
|
logger.push(f"目前解析测试项为:{self.name},其大纲标识为:{father_ident}")
|
||||||
|
# 用例初始化
|
||||||
|
self.csh = csh
|
||||||
|
# 属性 - step
|
||||||
|
self.step = []
|
||||||
|
for i, line in enumerate(content_list):
|
||||||
|
# content是数组,每一项是字典,包含'content'和'yuqi'
|
||||||
|
self.step.append({'counter': i + 1, 'content': line['content'], 'yuqi': line['yuqi']})
|
||||||
|
# case的ident需要进步
|
||||||
|
if has_r1:
|
||||||
|
self.ident = "R1_" + father_ident.replace("XQ", "YL") + "_" + str(cIdx + 1)
|
||||||
|
else:
|
||||||
|
self.ident = father_ident.replace("XQ", "YL") + "_" + str(cIdx + 1)
|
||||||
|
# 属性 - 综述
|
||||||
|
self.zong = zongsu
|
||||||
128
stuctor/ope.py
Normal file
128
stuctor/ope.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
from docx import Document
|
||||||
|
from docxtpl import DocxTemplate
|
||||||
|
from stuctor.constants import PREFIX
|
||||||
|
from docx.oxml.text.paragraph import CT_P
|
||||||
|
from docx.table import Table
|
||||||
|
from stuctor.item import TitleItem, TableItemList, Item
|
||||||
|
from nicegui import ui
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
template_path = Path.cwd() / "templates"
|
||||||
|
|
||||||
|
class Operator:
|
||||||
|
"""
|
||||||
|
参数1: 传入被解析文档名称,不加.docx
|
||||||
|
参数2: 传入说明模版文档名称,不加.docx
|
||||||
|
参数3: 传入记录模版文档名称,不加.docx
|
||||||
|
属性1: item_list - 储存所有Item对象的列表
|
||||||
|
"""
|
||||||
|
# 标题过滤
|
||||||
|
TITLE_LIST = ['3', '30', '4', '40', '5', '50', '6', '60', '7', '70']
|
||||||
|
BASIC_CAPTER = '6.2'
|
||||||
|
|
||||||
|
def __init__(self, doc_name: str, tp_shuoming_name: str, tp_jilu_name: str, logger: ui.log) -> None:
|
||||||
|
# 记录器
|
||||||
|
self.logger = logger
|
||||||
|
# 被解析文档对象
|
||||||
|
self.doc = Document(doc_name)
|
||||||
|
# 说明模版文件
|
||||||
|
self.tp_shuom = DocxTemplate(template_path / (tp_shuoming_name+'.docx'))
|
||||||
|
# 记录模版文件
|
||||||
|
self.tp_jilu = DocxTemplate(template_path / (tp_jilu_name+'.docx'))
|
||||||
|
# 储存所有item对象的列表
|
||||||
|
self.item_list: list[Item] = []
|
||||||
|
# 储存大纲章节号排布
|
||||||
|
self.c3 = 0
|
||||||
|
self.c4 = 0
|
||||||
|
self.c5 = 0
|
||||||
|
self.c6 = 0
|
||||||
|
self.c7 = 0
|
||||||
|
self.last_title_item = None
|
||||||
|
|
||||||
|
def main_parse(self, parse_content_strategy: str = 'FPGA'):
|
||||||
|
"""默认是未解析的,调用该函数开始解析"""
|
||||||
|
# 解析开始-这里有点耦合了,其实该请TitleItem对象去处理的
|
||||||
|
for ele in self.doc._element.body.iter(): # type: ignore
|
||||||
|
if ele.tag.endswith('}p'):
|
||||||
|
for child in ele.iter():
|
||||||
|
if child.tag.endswith("pStyle"):
|
||||||
|
rank = child.get(f"{PREFIX}val")
|
||||||
|
title = self.__parse_ele_title(ele)
|
||||||
|
# 标题只有30,40,5,6
|
||||||
|
if rank in self.TITLE_LIST:
|
||||||
|
current_title_item = TitleItem(rank, title)
|
||||||
|
self.item_list.append(current_title_item)
|
||||||
|
# 解析当前章节号
|
||||||
|
self.__parse_current_capter(current_title_item)
|
||||||
|
elif ele.tag.endswith('}tbl'):
|
||||||
|
table = Table(ele, self.doc)
|
||||||
|
if table.cell(0, 0).text == "测试项名称":
|
||||||
|
self.item_list.append(TableItemList(table, self.last_title_item, self.logger, parse_content_strategy))
|
||||||
|
|
||||||
|
# 私有:解析标题文字
|
||||||
|
def __parse_ele_title(self, ele: CT_P):
|
||||||
|
t_ele = ele.findall(f".//{PREFIX}t") # type: ignore
|
||||||
|
title = ''
|
||||||
|
for i in range(len(t_ele)):
|
||||||
|
if not t_ele[i].text.isdigit():
|
||||||
|
title = title + t_ele[i].text
|
||||||
|
return title
|
||||||
|
|
||||||
|
# 私有:解析当前章节号-根据其rank
|
||||||
|
def __parse_current_capter(self, current_title_item: TitleItem):
|
||||||
|
rank = current_title_item.type
|
||||||
|
if rank == '30' or rank == '3':
|
||||||
|
self.c3 += 1
|
||||||
|
self.c4 = 0
|
||||||
|
self.c5 = 0
|
||||||
|
self.c6 = 0
|
||||||
|
self.c7 = 0
|
||||||
|
current_title_item.capter = ".".join([self.BASIC_CAPTER, str(self.c3)])
|
||||||
|
elif rank == '40' or rank == '4':
|
||||||
|
self.c4 += 1
|
||||||
|
self.c5 = 0
|
||||||
|
self.c6 = 0
|
||||||
|
self.c7 = 0
|
||||||
|
current_title_item.capter = '.'.join([self.BASIC_CAPTER, str(self.c3), str(self.c4)])
|
||||||
|
elif rank == '50' or rank == '5':
|
||||||
|
self.c5 += 1
|
||||||
|
self.c6 = 0
|
||||||
|
self.c7 = 0
|
||||||
|
current_title_item.capter = '.'.join([self.BASIC_CAPTER, str(self.c3), str(self.c4), str(self.c5)])
|
||||||
|
elif rank == '60' or rank == '6':
|
||||||
|
self.c6 += 1
|
||||||
|
self.c7 = 0
|
||||||
|
current_title_item.capter = '.'.join([self.BASIC_CAPTER, str(self.c3), str(self.c4), str(self.c6)])
|
||||||
|
elif rank == '70' or rank == '7':
|
||||||
|
self.c7 += 1
|
||||||
|
current_title_item.capter = '.'.join([self.BASIC_CAPTER, str(self.c3), str(self.c4), str(self.c6), str(self.c7)])
|
||||||
|
|
||||||
|
self.last_title_item = current_title_item
|
||||||
|
|
||||||
|
# 最后渲染
|
||||||
|
def render(self):
|
||||||
|
context = self.__create_context()
|
||||||
|
self.tp_shuom.render(context, autoescape = True)
|
||||||
|
self.tp_shuom.save("测试说明-输出.docx")
|
||||||
|
self.tp_jilu.render(context, autoescape = True)
|
||||||
|
self.tp_jilu.save("测试记录-输出.docx")
|
||||||
|
|
||||||
|
# 构造context
|
||||||
|
def __create_context(self):
|
||||||
|
data_list = []
|
||||||
|
for item in self.item_list:
|
||||||
|
if isinstance(item, TableItemList):
|
||||||
|
for it in item.table_item:
|
||||||
|
temp_item = {
|
||||||
|
'type': 'table',
|
||||||
|
'name': it.name,
|
||||||
|
'ident': it.ident,
|
||||||
|
'zhui': item.zhui,
|
||||||
|
'zong': it.zong,
|
||||||
|
'step': it.step,
|
||||||
|
'csh': it.csh
|
||||||
|
}
|
||||||
|
data_list.append(temp_item)
|
||||||
|
elif isinstance(item, TitleItem):
|
||||||
|
data_list.append({'type': item.type, 'title': item.title})
|
||||||
|
return {"datas": data_list}
|
||||||
4
stuctor/patterns.py
Normal file
4
stuctor/patterns.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
class ParsePatterns:
|
||||||
|
step_pattern = re.compile(r"(([A-Z]+_)+")
|
||||||
20
stuctor/tools.py
Normal file
20
stuctor/tools.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
def normal_parse_content(paras: List[str], split_str = '通过仿真'):
|
||||||
|
content_list = []
|
||||||
|
front = []
|
||||||
|
rear = []
|
||||||
|
flag = True
|
||||||
|
for para in paras:
|
||||||
|
if flag:
|
||||||
|
split_para = para.rsplit(split_str, maxsplit = 1)
|
||||||
|
if len(split_para) > 1:
|
||||||
|
flag = False
|
||||||
|
front.append(split_para[0].strip(";;.。,, ") + '查看')
|
||||||
|
rear.append(*split_para[1:])
|
||||||
|
else:
|
||||||
|
front.append(split_para[0])
|
||||||
|
else:
|
||||||
|
rear.append(para)
|
||||||
|
content_list.append({'content': ''.join(front), 'yuqi': ''.join(rear).strip(";;.。,, ")})
|
||||||
|
return content_list
|
||||||
13
stuctor/wdsc.py
Normal file
13
stuctor/wdsc.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"""文档测试项描述和测试方法解析"""
|
||||||
|
|
||||||
|
def parse_wdsc(content: str):
|
||||||
|
"""输入为文档审查的测试项描述和测试方法"""
|
||||||
|
# 对content进行处理
|
||||||
|
content_list = []
|
||||||
|
for para in content.split('\n'):
|
||||||
|
step_dict = {'content': '', 'yuqi': ''}
|
||||||
|
if "《" in para or "》" in para:
|
||||||
|
step_dict['content'] = para.replace(';', '').replace(';', '')
|
||||||
|
step_dict['yuqi'] = '被审查文档内容完整、描述准确、格式规范、文文一致'
|
||||||
|
content_list.append(step_dict)
|
||||||
|
return content_list
|
||||||
BIN
templates/测试记录.docx
Normal file
BIN
templates/测试记录.docx
Normal file
Binary file not shown.
BIN
templates/测试说明.docx
Normal file
BIN
templates/测试说明.docx
Normal file
Binary file not shown.
BIN
测试记录-输出.docx
Normal file
BIN
测试记录-输出.docx
Normal file
Binary file not shown.
BIN
测试说明-输出.docx
Normal file
BIN
测试说明-输出.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user