diff --git a/Testlink转换工具/ExcelToXml2/ExcelToXml.py b/Testlink转换工具/ExcelToXml2/ExcelToXml.py
new file mode 100644
index 0000000..40d6f20
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml2/ExcelToXml.py
@@ -0,0 +1,435 @@
+# -*- coding:utf-8 -*-
+# 日期:2019-8-14
+# 作者:Wang WenJie
+# 版本:1.2.2
+# 说明:Excel转Xml,用于TestLink导入测试案例
+#*******************************************
+# 修改说明:
+# 2019-9-10:修复CDATA类型文件转换后无法被读取
+# 修改换行标记
+# 2019-10-22:修复模块中间出现空白行解析不全
+#*******************************************
+
+import xlrd
+import xml.dom.minidom as minidom
+import re
+import os
+import sys
+reload(sys)
+sys.setdefaultencoding('utf-8') #解决写入文件时候编码错误
+
+
+class ExcelToXml(object):
+ """
+ Excel转Xml,用于TestLink导入测试案例
+
+ 构造字典树,遍历写入xml文件
+ """
+
+ def __init__(self, file_name):
+ try:
+ self.__xl = xlrd.open_workbook(file_name, formatting_info = True)
+ except NotImplementedError:
+ raise ValueError(u'不支持xlsx格式文件')
+ self.__xml_tree = {} # 全局字典树
+ self.__result = [0, 0, 0] #sheet页,模块,测试案例
+ _, self.__filename = os.path.split(file_name)
+
+ def __get_importance(self, zh_str):
+ '''
+ 重要性中文转对应数字,不存在返回原字符
+
+ Args:
+ zh_str: 待转换字符
+ '''
+ if zh_str == u'中':
+ return '2'
+ elif zh_str == u'低':
+ return '1'
+ elif zh_str == u'高':
+ return '3'
+ # 都不存在返回原字符
+ return zh_str
+
+ def __get_exec_type(self, zh_str):
+ '''
+ 执行方式中文转对应数字,不存在返回原字符
+
+ Args:
+ zh_str: 待转换字符
+ '''
+ if zh_str == u'手工':
+ return '1'
+ elif zh_str == u'自动的':
+ return '2'
+ # 都不存在返回原字符
+ return zh_str
+
+ def __get_modules(self, col):
+ '''
+ 从各个sheet页中的模块名开始遍历Excel中的内容,构造字典树
+
+ Args:
+ col: 模块所在列
+
+ Returns:
+ 成功True, 失败False
+ '''
+ # 获取所有sheet页名称
+ sheet_names = self.__xl.sheet_names()
+ for sheet_name in sheet_names:
+ sheet = self.__xl.sheet_by_name(sheet_name)
+ nrows = sheet.nrows #总行数
+ # 没有数据直接忽略
+ if nrows < 2:
+ continue
+ tmp_index = (0, 1) # 第一行为标题
+ module_dic = {}
+ non_merged = [] # 未合并的行数
+ rest_merged = [] # 中间空白行引起的剩余模块
+ length = 1 # 记录遍历到多少行
+ for rlow, rhigh, clow, chigh in sheet.merged_cells:
+ # 不是第一列模块,忽略
+ if chigh != (col + 1):
+ continue
+ # 第一行没有标题
+ if rlow == 0:
+ tmp_index = (0, 0)
+ # 如果有单独一行的模块,或者没有合并单元格的模块
+ if rlow > tmp_index[1]:
+ non_merged.append((tmp_index[1], rlow))
+ else: # 合并的单元格
+ module_name = sheet.cell_value(rlow, clow)
+ # 空模块名默认是单例
+ if module_name.strip() == '':
+ continue
+ if module_dic.has_key(module_name):
+ print(u'ERROR:[{}-{}]合并单元格中有重复模块名[{}]'.format(self.__filename, sheet_name, module_name))
+ return False
+ module_dic[module_name] = {'coord': (rlow, rhigh)}
+ length = rhigh
+ tmp_index = (rlow, rhigh)
+ # 尾部剩余的没有合并单元格的添加进去
+ if length != nrows:
+ non_merged.append((length, nrows))
+ for i in range(0, len(non_merged) - 1):
+ _, rlow = non_merged[i]
+ _, rhigh = non_merged[i+1]
+ rest_merged.append((rlow, rhigh))
+ # 尾部还是单行的,或者没有合并的
+ start = 0
+ end = 1
+ last_name = ''
+ for rlow, rhigh in rest_merged:
+ module_name = sheet.cell_value(rlow, col)
+ # 如果该单元格有模块名
+ if module_name.strip() != '':
+ if module_dic.has_key(module_name):
+ # 解决读取BUG,会把Excel多个合并单元格全部读取出来
+ low, high = module_dic.get(module_name)['coord']
+ if rlow != low and rhigh != high:
+ print('ERROR:[{}-{}]有重复模块名[{}]'.format(self.__filename, sheet_name, module_name))
+ return False
+ else:
+ continue
+ # 记录模块行数坐标
+ module_dic[module_name] = {'coord': (rlow, rhigh)}
+ start = rlow
+ end = rhigh
+ last_name = module_name
+ else: # 空白行由于步骤引起的,属于上一个模块
+ if last_name.strip() == '':
+ continue
+ module_dic[last_name] = {'coord': (start, end+1)}
+ end = end + 1
+ # 单例模式
+ if last_name == '' and not module_dic:
+ module_dic['single_case'] = {'coord': (1, nrows)}
+ self.__xml_tree[sheet_name] = module_dic
+ return True
+
+ def __get_step(self, sheet_name, rlow, rhigh, col_action, col_result, execution_type):
+ '''
+ 获取标题对应的步骤和结果
+ 返回:step列表
+ '''
+ steps = []
+ step_num = 1
+ # sheet页实例
+ sheet = self.__xl.sheet_by_name(sheet_name)
+ for row in range(rlow, rhigh):
+ # 动作
+ action = sheet.cell_value(row, col_action)
+ # 结果
+ result = sheet.cell_value(row, col_result)
+ # 构造字典,键值和节点名称对应
+ data = {
+ 'step_number': str(step_num),
+ 'actions': self.__auto_break(action),
+ 'expectedresults': self.__auto_break(result),
+ 'execution_type': execution_type
+ }
+ step_num = step_num + 1
+ steps.append(data)
+ return steps
+
+ def __get_title(self, sheet_name, col):
+ '''
+ 获取sheet中模块对应的所有测试案例标题及起止行数
+
+ Args:
+ sheet_name: sheet页名称
+ col: 标题所在列数,从0计数
+
+ Returns:
+ 成功True, 失败False
+ '''
+ sheet = self.__xl.sheet_by_name(sheet_name)
+ for k, v in self.__xml_tree[sheet_name].items():
+ title_dic = {} # 标题字典
+ start = 0 # 记录起始行
+ end = 0 # 记录空白的终止行
+ curr_title = ''# 当前标题
+ for row in range(v['coord'][0], v['coord'][1]):
+ title = sheet.cell_value(row, col)
+ if title.strip() != '': # 如果是标题行
+ title_dic[title] = {'coord': (row, row + 1)}
+ start = row
+ end = row + 1
+ curr_title = title
+ else: # 如果是步骤引发的空格
+ end = end + 1
+ title_dic[curr_title] = {'coord': (start, end)}
+ self.__xml_tree[sheet_name][k]['testcase'] = title_dic
+ return True
+
+ def __auto_break(self, text):
+ '''
+ 实现步骤和结果自动换行
+
+ Args:
+ text: 待换行的字符床
+ '''
+ doc = re.sub(r'\n', r'
', text, re.M)
+ return doc
+
+ def create_tree(self):
+ '''
+ 根据TestLink格式要求构造字典树
+
+ Returns:
+ 成功True, 失败False
+ '''
+ if not self.__get_modules(0):
+ print(u'ERROR: 创建字典树中,读取模块失败')
+ return False
+ # sheet页模块
+ for k, v in self.__xml_tree.items():
+ # print('一级模块{}:'.format(k))
+ # sheet数量统计
+ self.__result[0] = self.__result[0] + 1
+ sheet = self.__xl.sheet_by_name(k)
+ if not self.__get_title(k, 1):
+ print(u'ERROR: 创建字典树中,读取标题失败')
+ # sheet页中的模块
+ for mk, mv in v.items():
+ # print('\t模块[{}]'.format(mk))
+ # 模块统计
+ self.__result[1] = self.__result[1] + 1
+ testcase = sorted(mv['testcase'].items(), key = lambda x:x[0])
+ for title, content in testcase:
+ # print('\t\t标题[{}]'.format(title))
+ # 测试案例统计
+ self.__result[2] = self.__result[2] + 1
+ row = content['coord'][0] # 标题所在行
+ summary = sheet.cell_value(row, 4) # 摘要
+ precond = sheet.cell_value(row, 5) # 前提
+ exe_type = self.__get_exec_type(sheet.cell_value(row, 3)) # 执行方式
+ importance = self.__get_importance(sheet.cell_value(row, 2)) # 重要性
+ # 步骤
+ steps = self.__get_step(k, content['coord'][0], content['coord'][1], 6, 7, exe_type)
+ case = {
+ 'node_order': str(self.__result[2]),
+ 'version': '1',
+ 'summary': self.__auto_break(summary),
+ 'preconditions': self.__auto_break(precond),
+ 'execution_type': exe_type,
+ 'importance': importance,
+ 'status': '1',
+ 'is_open': '1',
+ 'active': '1',
+ 'steps': steps
+ }
+ self.__xml_tree[k][mk]['testcase'][title]['case'] = case
+ return True
+
+ def get_tree(self):
+ '''
+ 返回构造好的字典树
+ '''
+ return self.__xml_tree
+
+ def write_xml(self, file_path):
+ '''
+ 把字典树导出为xml格式
+
+ Args:
+ file_path: xml文件保存路径
+
+ Returns:
+ 成功True, 失败False
+ '''
+ tree = sorted(self.__xml_tree.items(), key = lambda x:x[0])
+ for k, v in tree:
+ # 创建根节点
+ dom = XmlDom()
+ root = None
+ # 如果是单例模式
+ if v.has_key('single_case'):
+ root = dom.create_root('testcases')
+ else:
+ root = dom.create_root('testsuite')
+ # k-sheet页名
+ root.setAttribute('name', k)
+ # 遍历模块
+ order = 1
+ module_v = sorted(v.items(), key = lambda x:x[0])
+ for mk, mv in module_v:
+ # 如果不是单例模式,则创建模块节点
+ module = None
+ if mk != 'single_case':
+ module = dom.create_node('testsuite')
+ # mk-模块名
+ module.setAttribute('name', mk)
+ root.appendChild(module)
+ module_order = dom.create_cdata('node_order', str(order))
+ module.appendChild(module_order)
+ order = order + 1
+ else: # 单例模式模块节点用根节点覆盖
+ module = root
+ case_title = sorted(mv['testcase'].items(), key = lambda x:x[0])
+ for title, cases in case_title:
+ # 标题案例节点
+ testcase = dom.create_node('testcase')
+ testcase.setAttribute('name', title)
+ module.appendChild(testcase)
+ for node, text in cases['case'].items():
+ # 非step节点直接写入
+ if node != 'steps':
+ tmp = dom.create_cdata(node, text)
+ testcase.appendChild(tmp)
+ else:
+ steps = dom.create_node(node)
+ testcase.appendChild(steps)
+ # steps是一个列表
+ for step_list in text:
+ step = dom.create_node('step')
+ steps.appendChild(step)
+ # 创建steps中的单个step
+ for node, text in step_list.items():
+ tmp = dom.create_cdata(node, text)
+ step.appendChild(tmp)
+ # 创建输出文件名
+ file_name = ''
+ if v.has_key('single_case'):
+ file_name = u'Example_Testsuite_Default_%s_Testcases.xml' % k
+ else:
+ file_name = u'Example_Testsuite_Default_%s_Testsuite.xml' % k
+ path = os.path.join(file_path.decode('gb2312'), file_name)
+ # 写入文件
+ dom.write(path)
+ return True
+
+ def get_result(self):
+ '''
+ 返回转换sheet、模块和测试案例统计
+ '''
+ return self.__result
+
+
+class XmlDom(object):
+ """
+ 创建xml文件方法,使用minidom方式创建
+ """
+
+ def __init__(self):
+ self.dom = None
+
+ def create_root(self, name):
+ '''
+ 创建根节点
+
+ Args:
+ name: 根节点名称
+ '''
+ self.dom = minidom.getDOMImplementation().createDocument(None, name, None)
+ root = self.dom.documentElement
+ return root
+
+ def create_node(self, name):
+ '''
+ 创建一个节点
+
+ Args:
+ name: 节点名称
+ '''
+ node = self.dom.createElement(name)
+ return node
+
+ def create_cdata(self, name, text=''):
+ """
+ 创建CDATA数据节点
+
+ Args:
+ name: 节点名
+ text: 写入节点的值
+
+ Returns:
+ 节点对象
+ """
+ node = self.dom.createElement(name)
+ cdata_text = self.dom.createCDATASection(text)
+ node.appendChild(cdata_text)
+ return node
+
+ def create_text(self, name, text=''):
+ """
+ 创建普通Text数据节点
+
+ Args:
+ name: 节点名
+ text: 写入节点的值
+
+ Returns:
+ 节点对象
+ """
+ node = self.dom.createElement(name)
+ cdata_text = self.dom.createTextNode(text)
+ node.appendChild(cdata_text)
+ return node
+
+ def write(self, file_name):
+ '''
+ 写入xml文件
+
+ Args:
+ file_name: 写入的文件名
+ '''
+ # 先写入
+ with open(file_name, 'w') as f:
+ xml_str = self.dom.toprettyxml()
+ pretty = self.__pretty_xml(xml_str)
+ f.write(pretty)
+ # 释放
+ self.dom.unlink()
+
+ def __pretty_xml(self, xml_doc):
+ '''
+ 格式化xml中的CDATA
+ '''
+ doc = re.sub(r'>\n<\!\[', r'>(\s+)<', r']]><', doc, 0, re.M)
+ return doc
+
+if __name__ == "__main__":
+ print(u'Excel转XML助手,用于TestLink导入测试案例')
\ No newline at end of file
diff --git a/Testlink转换工具/ExcelToXml2/check.py b/Testlink转换工具/ExcelToXml2/check.py
new file mode 100644
index 0000000..beda635
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml2/check.py
@@ -0,0 +1,32 @@
+# -*- encoding: utf-8 -*-
+import os
+import sys
+
+
+# 用于环境检查
+# 检查Python版本
+# 检查Python模块,需要xlrd
+def check_env():
+
+ # 检查input文件夹
+ input_dir = os.path.join(os.getcwd(), 'input')
+ if not os.path.exists(input_dir):
+ os.mkdir(input_dir)
+
+ # 检查xlrd模块
+ try:
+ import xlrd
+ except:
+ print(u'缺少xlrd模块,将自动安装...')
+ # 执行安装
+ p = os.popen('pip install xlrd')
+ print(p.read())
+ import xlrd
+ finally:
+ print('***************************')
+ print(u'环境配置正常,可以运行转换工具')
+ print('***************************')
+
+if __name__ == "__main__":
+ check_env()
+
diff --git a/Testlink转换工具/ExcelToXml2/converter.py b/Testlink转换工具/ExcelToXml2/converter.py
new file mode 100644
index 0000000..47c374b
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml2/converter.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+from ExcelToXml import ExcelToXml
+import os
+import sys
+
+
+# 定义后缀过滤列表
+filter_list = ['.xls']
+
+# 说明:检查output文件夹是否存在,不存在则创建
+# 参数:无
+# 返回值:输出路径
+def get_output_dir():
+ output = os.path.join(os.getcwd(), 'output')
+ if not os.path.exists(output):
+ os.mkdir(output)
+ return output
+
+
+# 说明:遍历指定文件夹扫描Excel文件
+# 参数:无
+# 返回值:输出文件夹下所有文件路径列表
+def get_input_excel(input_dir):
+ files = []
+ # input文件夹不存在
+ if not os.path.exists(input_dir):
+ return files
+ else: # 遍历该目录下所有文件
+ for _, _, file_list in os.walk(input_dir):
+ for filename in file_list:
+ file_path = os.path.join(input_dir, filename)
+ # 获取文件后缀
+ ext = os.path.splitext(file_path)[1]
+ # 满足要求的则放入
+ if ext in filter_list:
+ files.append(file_path)
+ return files
+
+# 将命令行参数转换为对应的文件输入
+def get_command():
+ excels = []
+ input_dir = ''
+ # 未传入参数,使用默认设置
+ if len(sys.argv) == 1:
+ input_dir = os.path.join(os.getcwd(), 'input')
+ excels = get_input_excel(input_dir)
+ elif len(sys.argv) == 2: # 传入了参数
+ input_dir = sys.argv[1]
+ # 如果传入的是单个文件
+ if os.path.isfile(input_dir):
+ ext = os.path.splitext(input_dir)[1]
+ # 判断是否是Excel
+ if ext not in filter_list:
+ print(u'ERROR:请传入正确的Excel文件')
+ return False
+ excels.append(input_dir)
+ elif os.path.exists(input_dir): # 如果是文件夹
+ excels = get_input_excel(input_dir)
+ return excels, input_dir
+
+
+# 说明:将指定Excel转换为TestLink使用的xml文件
+# 参数:无
+# 返回值:成功返回True
+def converter():
+ # 获取命令行参数
+ excels, input_dir = get_command()
+ # 校验文件输入
+ if len(excels) == 0:
+ print(u'ERROR: 未发现输入的Excel文件')
+ return False
+ print('--------------------------------')
+ print u'INFO: 当前输入文件为:%s' % input_dir.decode('gb2312')
+ output = get_output_dir()
+ # 遍历文件输出
+ print(u'INFO: 开始执行Excel转xml...')
+ print('--------------------------------')
+ for excel in excels:
+ # 文件名
+ _, filename = os.path.split(excel)
+ # 实例化对象
+ xm = ExcelToXml(excel)
+ if not xm.create_tree():
+ print 'ERROR: [%s]转换发生错误' % filename.decode('gb2312')
+ print('------------------------')
+ continue
+ # 写入xml文件
+ xm.write_xml(output)
+ result = xm.get_result()
+ print u'[%s]转换成功!' % filename.decode('gb2312')
+ print u'Sheet总数为:\t%s' % result[0]
+ print u'模块总数为:\t%s' % result[1]
+ print u'案例总数为:\t%s' % result[2]
+ print('------------------------')
+ print u'INFO: 全部转换成功,已输出到文件夹:%s' % output.decode('gb2312')
+ return True
+
+
+if __name__ == "__main__":
+ # 开始转换
+ converter()
\ No newline at end of file
diff --git a/Testlink转换工具/ExcelToXml2/readme.txt b/Testlink转换工具/ExcelToXml2/readme.txt
new file mode 100644
index 0000000..9b2c3d2
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml2/readme.txt
@@ -0,0 +1,20 @@
+Ҫ
+1Python 2.x
+2ģxlrd
+ʹ python check.py ɰ汾ģ鰲װ
+
+
+в
+1python converter.py
+Excelļinputļ£xmlļoutputļ£Զ
+2python converter.py ļ·
+ָļУļ·пոxmlļoutputļ
+3python converter.py Excelļ·
+ָļxmlļoutputļ
+
+
+ģҪ֧xlsxʽExcel
+1Ŀ¼ṹģʽ
+xmlṹᰴաsheetҳ->ģ->⡿TestLinkTestLinkûжӦĿ¼Զ
+2ģʽ
+ExcelģΪհףɵxmlڵ뵥Ŀ¼
\ No newline at end of file
diff --git a/Testlink转换工具/ExcelToXml3/ExcelToXml.py b/Testlink转换工具/ExcelToXml3/ExcelToXml.py
new file mode 100644
index 0000000..5697a25
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml3/ExcelToXml.py
@@ -0,0 +1,412 @@
+# -*- coding:utf-8 -*-
+# 日期:2019-8-14
+# 作者:Wang WenJie
+# 版本:1.2.2
+# 说明:Excel转Xml,用于TestLink导入测试案例
+#*******************************************
+# 修改说明:
+# 2019-9-10:修复CDATA类型文件转换后无法被读取
+# 修改换行标记
+# 2019-10-22:修复模块中间出现空白行解析不全
+#*******************************************
+
+import xlrd
+import xml.dom.minidom as minidom
+import re
+import os
+
+
+class ExcelToXml(object):
+ """
+ Excel转Xml,用于TestLink导入测试案例
+
+ 构造字典树,遍历写入xml文件
+ """
+
+ def __init__(self, file_name):
+ try:
+ self.__xl = xlrd.open_workbook(file_name, formatting_info = True)
+ except NotImplementedError:
+ raise ValueError('不支持xlsx格式文件')
+ self.__xml_tree = {} # 全局字典树
+ self.__result = [0, 0, 0] #sheet页,模块,测试案例
+ _, self.__filename = os.path.split(file_name)
+
+ def __get_importance(self, zh_str):
+ '''
+ 重要性中文转对应数字,不存在返回原字符
+
+ Args:
+ zh_str: 待转换字符
+ '''
+ if zh_str == '中':
+ return '2'
+ elif zh_str == '低':
+ return '1'
+ elif zh_str == '高':
+ return '3'
+ # 都不存在返回原字符
+ return zh_str
+
+ def __get_exec_type(self, zh_str):
+ '''
+ 执行方式中文转对应数字,不存在返回原字符
+
+ Args:
+ zh_str: 待转换字符
+ '''
+ if zh_str == '手工':
+ return '1'
+ elif zh_str == '自动的':
+ return '2'
+ # 都不存在返回原字符
+ return zh_str
+
+ def __get_modules(self, col):
+ '''
+ 从各个sheet页中的模块名开始遍历Excel中的内容,构造字典树
+
+ Args:
+ col: 模块所在列
+
+ Returns:
+ 成功True, 失败False
+ '''
+ # 获取所有sheet页名称
+ sheet_names = self.__xl.sheet_names()
+ for sheet_name in sheet_names:
+ sheet = self.__xl.sheet_by_name(sheet_name)
+ nrows = sheet.nrows #总行数
+ # 没有数据直接忽略
+ if nrows < 2:
+ continue
+ tmp_index = (0, 1) # 第一行为标题
+ module_dic = {}
+ non_merged = [] # 未合并的行数
+ rest_merged = [] # 中间空白行引起的剩余模块
+ length = 1 # 记录遍历到多少行
+ for rlow, rhigh, clow, chigh in sheet.merged_cells:
+ # 不是第一列模块,忽略
+ if chigh != (col + 1):
+ continue
+ # 第一行没有标题
+ if rlow == 0:
+ tmp_index = (0, 0)
+ # 如果有单独一行的模块,或者没有合并单元格的模块
+ if rlow > tmp_index[1]:
+ non_merged.append((tmp_index[1], rlow))
+ elif rlow == tmp_index[1]: # 合并的单元格
+ module_name = sheet.cell_value(rlow, clow)
+ # 空模块名默认是单例
+ if module_name.strip() == '':
+ continue
+ if module_dic.__contains__(module_name):
+ print('ERROR:[{}-{}]合并单元格中有重复模块名[{}]'.format(self.__filename, sheet_name, module_name))
+ return False
+ module_dic[module_name] = {'coord': (rlow, rhigh)}
+ length = rhigh
+ tmp_index = (rlow, rhigh)
+ # 尾部剩余的没有合并单元格的添加进去
+ if length != nrows:
+ non_merged.append((length, nrows))
+ for i in range(0, len(non_merged) - 1):
+ _, rlow = non_merged[i]
+ _, rhigh = non_merged[i+1]
+ rest_merged.append((rlow, rhigh))
+ # 尾部还是单行的,或者没有合并的
+ start = 0
+ end = 1
+ last_name = ''
+ for rlow, rhigh in rest_merged:
+ module_name = sheet.cell_value(rlow, col)
+ # 如果该单元格有模块名
+ if module_name.strip() != '':
+ if module_dic.__contains__(module_name):
+ # 解决读取BUG,会把Excel多个合并单元格全部读取出来
+ low, high = module_dic.get(module_name)['coord']
+ if rlow != low and rhigh != high:
+ print('ERROR:[{}-{}]有重复模块名[{}]'.format(self.__filename, sheet_name, module_name))
+ return False
+ else:
+ continue
+ # 记录模块行数坐标
+ module_dic[module_name] = {'coord': (rlow, rhigh)}
+ start = rlow
+ end = rhigh
+ last_name = module_name
+ else: # 空白行由于步骤引起的,属于上一个模块
+ if last_name.strip() == '':
+ continue
+ module_dic[last_name] = {'coord': (start, end+1)}
+ end = end + 1
+ # 单例模式
+ if last_name == '' and not module_dic:
+ module_dic['single_case'] = {'coord': (1, nrows)}
+ self.__xml_tree[sheet_name] = module_dic
+ return True
+
+ def __get_step(self, sheet_name, rlow, rhigh, col_action, col_result, execution_type):
+ '''
+ 获取标题对应的步骤和结果
+ 返回:step列表
+ '''
+ steps = []
+ step_num = 1
+ # sheet页实例
+ sheet = self.__xl.sheet_by_name(sheet_name)
+ for row in range(rlow, rhigh):
+ # 动作
+ action = sheet.cell_value(row, col_action)
+ # 结果
+ result = sheet.cell_value(row, col_result)
+ # 构造字典,键值和节点名称对应
+ data = {
+ 'step_number': str(step_num),
+ 'actions': self.__auto_break(action),
+ 'expectedresults': self.__auto_break(result),
+ 'execution_type': execution_type
+ }
+ step_num = step_num + 1
+ steps.append(data)
+ return steps
+
+ def __get_title(self, sheet_name, col):
+ '''
+ 获取sheet中模块对应的所有测试案例标题及起止行数
+
+ Args:
+ sheet_name: sheet页名称
+ col: 标题所在列数,从0计数
+
+ Returns:
+ 成功True, 失败False
+ '''
+ sheet = self.__xl.sheet_by_name(sheet_name)
+ for k, v in self.__xml_tree[sheet_name].items():
+ title_dic = {} # 标题字典
+ start = 0 # 记录起始行
+ end = 0 # 记录空白的终止行
+ curr_title = ''# 当前标题
+ for row in range(v['coord'][0], v['coord'][1]):
+ title = sheet.cell_value(row, col)
+ if title.strip() != '': # 如果是标题行
+ title_dic[title] = {'coord': (row, row + 1)}
+ start = row
+ end = row + 1
+ curr_title = title
+ else: # 如果是步骤引发的空格
+ end = end + 1
+ title_dic[curr_title] = {'coord': (start, end)}
+ self.__xml_tree[sheet_name][k]['testcase'] = title_dic
+ return True
+
+ def __auto_break(self, text):
+ '''
+ 实现步骤和结果自动换行
+
+ Args:
+ text: 待换行的字符床
+ '''
+ doc = re.sub(r'\n', r'
', text, re.M)
+ return doc
+
+ def create_tree(self):
+ '''
+ 根据TestLink格式要求构造字典树
+
+ Returns:
+ 成功True, 失败False
+ '''
+ if not self.__get_modules(0):
+ print('ERROR: 创建字典树中,读取模块失败')
+ return False
+ # sheet页模块
+ for k, v in self.__xml_tree.items():
+ # print('一级模块{}:'.format(k))
+ # sheet数量统计
+ self.__result[0] = self.__result[0] + 1
+ sheet = self.__xl.sheet_by_name(k)
+ if not self.__get_title(k, 1):
+ print('ERROR: 创建字典树中,读取标题失败')
+ # sheet页中的模块
+ for mk, mv in v.items():
+ # print('\t模块[{}]'.format(mk))
+ # 模块统计
+ self.__result[1] = self.__result[1] + 1
+ for title, content in mv['testcase'].items():
+ # print('\t\t标题[{}]'.format(title))
+ # 测试案例统计
+ self.__result[2] = self.__result[2] + 1
+ row = content['coord'][0] # 标题所在行
+ summary = sheet.cell_value(row, 4) # 摘要
+ precond = sheet.cell_value(row, 5) # 前提
+ exe_type = self.__get_exec_type(sheet.cell_value(row, 3)) # 执行方式
+ importance = self.__get_importance(sheet.cell_value(row, 2)) # 重要性
+ # 步骤
+ steps = self.__get_step(k, content['coord'][0], content['coord'][1], 6, 7, exe_type)
+ case = {
+ 'node_order': str(self.__result[2]),
+ 'version': '1',
+ 'summary': self.__auto_break(summary),
+ 'preconditions': self.__auto_break(precond),
+ 'execution_type': exe_type,
+ 'importance': importance,
+ 'status': '1',
+ 'is_open': '1',
+ 'active': '1',
+ 'steps': steps
+ }
+ self.__xml_tree[k][mk]['testcase'][title]['case'] = case
+ return True
+
+ def get_tree(self):
+ '''
+ 返回构造好的字典树
+ '''
+ return self.__xml_tree
+
+ def write_xml(self, file_path):
+ '''
+ 把字典树导出为xml格式
+
+ Args:
+ file_path: xml文件保存路径
+
+ Returns:
+ 成功True, 失败False
+ '''
+ for k, v in self.__xml_tree.items():
+ # 创建根节点
+ dom = XmlDom()
+ root = None
+ # 如果是单例模式
+ if v.__contains__('single_case'):
+ root = dom.create_root('testcases')
+ else:
+ root = dom.create_root('testsuite')
+ # k-sheet页名
+ root.setAttribute('name', k)
+ # 遍历模块
+ order = 1
+ for mk, mv in v.items():
+ # 如果不是单例模式,则创建模块节点
+ module = None
+ if mk != 'single_case':
+ module = dom.create_node('testsuite')
+ # mk-模块名
+ module.setAttribute('name', mk)
+ root.appendChild(module)
+ module_order = dom.create_cdata('node_order', str(order))
+ module.appendChild(module_order)
+ order = order + 1
+ else: # 单例模式模块节点用根节点覆盖
+ module = root
+ for title, cases in mv['testcase'].items():
+ # 标题案例节点
+ testcase = dom.create_node('testcase')
+ testcase.setAttribute('name', title)
+ module.appendChild(testcase)
+ for node, text in cases['case'].items():
+ # 非step节点直接写入
+ if node != 'steps':
+ tmp = dom.create_cdata(node, text)
+ testcase.appendChild(tmp)
+ else:
+ steps = dom.create_node(node)
+ testcase.appendChild(steps)
+ # steps是一个列表
+ for step_list in text:
+ step = dom.create_node('step')
+ steps.appendChild(step)
+ # 创建steps中的单个step
+ for node, text in step_list.items():
+ tmp = dom.create_cdata(node, text)
+ step.appendChild(tmp)
+ # 创建输出文件名
+ file_name = ''
+ if v.__contains__('single_case'):
+ file_name = r'Example_Testsuite_Default_{}_Testcases.xml'.format(k)
+ else:
+ file_name = r'Example_Testsuite_Default_{}_Testsuite.xml'.format(k)
+ path = os.path.join(file_path, file_name)
+ # 写入文件
+ dom.write(path)
+ return True
+
+ def get_result(self):
+ '''
+ 返回转换sheet、模块和测试案例统计
+ '''
+ return self.__result
+
+
+class XmlDom(object):
+ """
+ 创建xml文件方法,使用minidom方式创建
+ """
+
+ def __init__(self):
+ self.dom = None
+
+ def create_root(self, name):
+ '''
+ 创建根节点
+
+ Args:
+ name: 根节点名称
+ '''
+ self.dom = minidom.getDOMImplementation().createDocument(None, name, None)
+ root = self.dom.documentElement
+ return root
+
+ def create_node(self, name):
+ '''
+ 创建一个节点
+
+ Args:
+ name: 节点名称
+ '''
+ node = self.dom.createElement(name)
+ return node
+
+ def create_cdata(self, name, text=''):
+ """
+ 创建CDATA数据节点
+
+ Args:
+ name: 节点名
+ text: 写入节点的值
+
+ Returns:
+ 节点对象
+ """
+ node = self.dom.createElement(name)
+ cdata_text = self.dom.createCDATASection(text)
+ node.appendChild(cdata_text)
+ return node
+
+ def write(self, file_name):
+ '''
+ 写入xml文件
+
+ Args:
+ file_name: 写入的文件名
+ '''
+ # 先写入
+ with open(file_name, 'w', encoding='utf-8') as f:
+ xml_str = self.dom.toprettyxml()
+ pretty = self.__pretty_xml(xml_str)
+ f.write(pretty)
+ # 释放
+ self.dom.unlink()
+
+ def __pretty_xml(self, xml_doc):
+ '''
+ 格式化xml中的CDATA
+ '''
+ doc = re.sub(r'>\n<\!\[', r'>(\s+)<', r']]><', doc, 0, re.M)
+ return doc
+
+if __name__ == "__main__":
+ print('Excel转XML助手,用于TestLink导入测试案例')
\ No newline at end of file
diff --git a/Testlink转换工具/ExcelToXml3/check.py b/Testlink转换工具/ExcelToXml3/check.py
new file mode 100644
index 0000000..d7dabca
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml3/check.py
@@ -0,0 +1,37 @@
+import os
+import sys
+
+
+# 用于环境检查
+# 检查Python版本
+# 检查Python模块,需要xlrd
+def check_env():
+ # 检查Python版本
+ if sys.version_info < (3, 0):
+ print('***************************')
+ print('ERROR: Python版本低于3.0')
+ print('***************************')
+ return
+
+ # 检查input文件夹
+ input_dir = os.path.join(os.getcwd(), 'input')
+ if not os.path.exists(input_dir):
+ os.mkdir(input_dir)
+
+ # 检查xlrd模块
+ try:
+ import xlrd
+ except:
+ print('缺少xlrd模块,将自动安装...')
+ # 执行安装
+ p = os.popen('pip install xlrd')
+ print(p.read())
+ import xlrd
+ finally:
+ print('***************************')
+ print('环境配置正常,可以运行转换工具')
+ print('***************************')
+
+if __name__ == "__main__":
+ check_env()
+
diff --git a/Testlink转换工具/ExcelToXml3/converter.py b/Testlink转换工具/ExcelToXml3/converter.py
new file mode 100644
index 0000000..aa64155
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml3/converter.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+from ExcelToXml import ExcelToXml
+import sys
+import os
+
+# 定义后缀过滤列表
+filter_list = ['.xls']
+
+# 说明:检查output文件夹是否存在,不存在则创建
+# 参数:无
+# 返回值:输出路径
+def get_output_dir():
+ output = os.path.join(os.getcwd(), 'output')
+ if not os.path.exists(output):
+ os.mkdir(output)
+ return output
+
+
+# 说明:遍历指定文件夹扫描Excel文件
+# 参数:无
+# 返回值:输出文件夹下所有文件路径列表
+def get_input_excel(input_dir):
+ files = []
+ # input文件夹不存在
+ if not os.path.exists(input_dir):
+ return files
+ else: # 遍历该目录下所有文件
+ for _, _, file_list in os.walk(input_dir):
+ for filename in file_list:
+ file_path = os.path.join(input_dir, filename)
+ # 获取文件后缀
+ ext = os.path.splitext(file_path)[1]
+ # 满足要求的则放入
+ if ext in filter_list:
+ files.append(file_path)
+ return files
+
+# 将命令行参数转换为对应的文件输入
+def get_command():
+ excels = []
+ input_dir = ''
+ # 未传入参数,使用默认设置
+ if len(sys.argv) == 1:
+ input_dir = os.path.join(os.getcwd(), 'input')
+ excels = get_input_excel(input_dir)
+ elif len(sys.argv) == 2: # 传入了参数
+ input_dir = sys.argv[1]
+ # 如果传入的是单个文件
+ if os.path.isfile(input_dir):
+ ext = os.path.splitext(input_dir)[1]
+ # 判断是否是Excel
+ if ext not in filter_list:
+ print('ERROR:请传入正确的Excel文件')
+ return False
+ excels.append(input_dir)
+ elif os.path.exists(input_dir): # 如果是文件夹
+ excels = get_input_excel(input_dir)
+ return excels, input_dir
+
+
+# 说明:将指定Excel转换为TestLink使用的xml文件
+# 参数:无
+# 返回值:成功返回True
+def converter():
+ # 获取命令行参数
+ excels, input_dir = get_command()
+ # 校验文件输入
+ if len(excels) == 0:
+ print('ERROR: 未发现输入的Excel文件')
+ return False
+ print('--------------------------------')
+ print('INFO: 当前输入文件为:', input_dir)
+ output = get_output_dir()
+ # 遍历文件输出
+ print('INFO: 开始执行Excel转xml...')
+ print('--------------------------------')
+ for excel in excels:
+ # 文件名
+ _, filename = os.path.split(excel)
+ # 实例化对象
+ xm = ExcelToXml(excel)
+ if not xm.create_tree():
+ print(u'ERROR: [{}]转换发生错误'.format(filename))
+ print('------------------------')
+ continue
+ # 写入xml文件
+ xm.write_xml(output)
+ result = xm.get_result()
+ print('[{}]转换成功!'.format(filename))
+ print('Sheet总数为:\t', result[0])
+ print('模块总数为:\t', result[1])
+ print('案例总数为:\t', result[2])
+ print('------------------------')
+ print('INFO: 全部转换成功,已输出到文件夹:', output)
+ return True
+
+
+if __name__ == "__main__":
+ # 开始转换
+ converter()
\ No newline at end of file
diff --git a/Testlink转换工具/ExcelToXml3/readme.txt b/Testlink转换工具/ExcelToXml3/readme.txt
new file mode 100644
index 0000000..72a6aea
--- /dev/null
+++ b/Testlink转换工具/ExcelToXml3/readme.txt
@@ -0,0 +1,20 @@
+Ҫ
+1Python 3.x
+2ģxlrd
+ʹ python check.py ɰ汾ģ鰲װ
+
+
+в
+1python converter.py
+Excelļinputļ£xmlļoutputļ£Զ
+2python converter.py ļ·
+ָļУļ·пոxmlļoutputļ
+3python converter.py Excelļ·
+ָļxmlļoutputļ
+
+
+ģҪ֧xlsxʽExcel
+1Ŀ¼ṹģʽ
+xmlṹᰴաsheetҳ->ģ->⡿TestLinkTestLinkûжӦĿ¼Զ
+2ģʽ
+ExcelģΪհףɵxmlڵ뵥Ŀ¼
\ No newline at end of file
diff --git a/Testlink转换工具/XmlToExcel2/XmlToExcel.py b/Testlink转换工具/XmlToExcel2/XmlToExcel.py
new file mode 100644
index 0000000..45b2983
--- /dev/null
+++ b/Testlink转换工具/XmlToExcel2/XmlToExcel.py
@@ -0,0 +1,285 @@
+# -*- coding:utf-8 -*-
+# 日期:2019年9月9日
+# 作者:Wang WenJie
+# 版本:1.2.1
+# 说明:用于TestLink导出后的Xml文件转成Excel文件
+# 支持二级模块
+
+import xlwt
+from xml.dom.minidom import parse, Node
+import os
+import re
+
+class XmlToExcel(object):
+ '''
+ TestLink导出的xml文件转Excel
+ 构造字典树读取xml文件
+ '''
+
+ def __init__(self):
+ # 全局字典树
+ self.__xml_tree = []
+
+ def __getText(self, node):
+ '''
+ 获取节点的文本内容
+
+ Args:
+ node: 节点
+
+ Returns:
+ 节点中的文本
+ '''
+ if node is None:
+ return ''
+ if node.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]:
+ # return node.nodeValue
+ return node.wholeText
+ else:
+ return ''
+
+ def __get_importance(self, num):
+ '''
+ 重要性序号转换成对应的中文
+ '''
+ if num == '1':
+ return u'低'
+ elif num == '2':
+ return u'中'
+ elif num == '3':
+ return u'高'
+ return num
+
+
+ def __get_exec_type(self, num):
+ '''
+ 执行方式序号转换成对应的中文
+ '''
+ if num == '1':
+ return u'手工'
+ elif num == '2':
+ return u'自动的'
+ return num
+
+ def __read_cases(self, case):
+ '''
+ 读单个testcase节点,并返回字典
+ '''
+ case_dic = {}
+ # 节点名称,即对应的标题
+ case_dic['casename'] = case.getAttribute('name')
+ case_row = 0
+ for child in case.childNodes:
+ # 如果遇到换行会读出TEXT_NODE,所以加上判断
+ if child.nodeType == Node.ELEMENT_NODE:
+ # 摘要和前提
+ if child.tagName in ['summary', 'preconditions']:
+ case_dic[child.tagName] = self.__getText(child.firstChild)
+ elif child.tagName == 'importance': # 重要性
+ num = self.__getText(child.firstChild)
+ case_dic[child.tagName] = self.__get_importance(num)
+ elif child.tagName == 'execution_type': # 执行方式
+ num = self.__getText(child.firstChild)
+ case_dic[child.tagName] = self.__get_exec_type(num)
+ elif child.tagName == 'steps': # 步骤
+ case_dic['steps'] = []
+ for steps in child.getElementsByTagName('step'):
+ step_dic = {}
+ case_row = case_row + 1
+ for step in steps.childNodes:
+ if step.nodeType == Node.ELEMENT_NODE:
+ if step.tagName in ['actions', 'expectedresults']:
+ step_dic[step.tagName] = self.__getText(step.firstChild)
+ case_dic['steps'].append(step_dic)
+ # 记录有多少步
+ case_dic['row'] = case_row
+ return case_dic
+
+ def __read_suites(self, root):
+ '''
+ 读模块下的testsuite
+ '''
+ suites = []
+ # 处理二级模块情况
+ if len(root.getElementsByTagName('testsuite')) == 0:
+ suites.append(self._read_module(root))
+ return suites
+ for testsuite in root.getElementsByTagName('testsuite'):
+ suites.append(self._read_module(testsuite))
+ return suites
+
+ def _read_module(self, parent):
+ module_tree = {}
+ # 模块名
+ module = parent.getAttribute('name')
+ module_tree['module'] = module
+ module_tree['testcase'] = []
+ module_row = 0
+ for case in parent.getElementsByTagName('testcase'):
+ case_dic = self.__read_cases(case)
+ module_row = module_row + case_dic['row']
+ module_tree['testcase'].append(case_dic)
+ # 记录模块占多少行
+ module_tree['row'] = module_row
+ return module_tree
+
+ def __read_single(self, root):
+ '''
+ 读单节点用例
+ '''
+ suites = []
+ module_tree = {}
+ module_tree['module'] = ''
+ module_tree['testcase'] = []
+ module_row = 0
+ testcase = root.getElementsByTagName('testcase')
+ for case in testcase:
+ case_dic = self.__read_cases(case)
+ module_row = module_row + case_dic['row']
+ # 记录模块占多少行
+ module_tree['row'] = module_row
+ suites.append(module_tree)
+ return suites
+
+
+ def read_xml(self, xml_files):
+ '''
+ 读xml文件主要方法
+
+ Args:
+ xml_files: xml文件路径
+ '''
+ # 统计
+ result = {}
+ result['success'] = 0
+ result['fail'] = 0
+ for file in xml_files:
+ xml_dic = {}
+ try:
+ dom = parse(file)
+ except:
+ print('ERROR: 载入文件失败[{}]'.format(file))
+ result['fail'] = result['fail'] + 1
+ continue
+ # 选择根节点
+ root = dom.documentElement
+ if root.nodeName == 'testsuite': # 多用例集
+ suites = self.__read_suites(root)
+ # 一级模块名,即sheet页名
+ sheet_name = root.getAttribute('name')
+ if sheet_name.strip() == '':
+ raise ValueError("The root name of xml cann't be null. Please check the name attruite of testsuite in line 1 or 2.");
+ elif root.nodeName == 'testcases': # 单个用例
+ suites = self.__read_single(root)
+ sheet_name = 'single'
+ else:
+ print('ERROR: 文件不是TestLink标准格式')
+ result['fail'] = result['fail'] + 1
+ continue
+
+ xml_dic[sheet_name] = suites
+ xml_dic['sheet_name'] = sheet_name
+ self.__xml_tree.append(xml_dic)
+ result['success'] = result['success'] + 1
+ return result
+
+
+ def set_style(self, height = 12, align = 'center'):
+ '''
+ 设置写入单元格样式,包括文字大小,对齐方式
+ '''
+ # 字体样式
+ font = xlwt.Font()
+ font.height = height * 20
+ font.name = u'宋体'
+ # 对齐方式
+ alignment = xlwt.Alignment()
+ # 居中
+ if align == 'center':
+ alignment.horz = xlwt.Alignment.HORZ_CENTER
+ elif align == 'left': # 左对齐
+ alignment.horz = xlwt.Alignment.HORZ_LEFT
+ alignment.vert = xlwt.Alignment.VERT_CENTER
+ alignment.wrap = 1 # 自动换行
+ style = xlwt.XFStyle()
+ style.alignment = alignment
+ style.font = font
+ return style
+
+ def __auto_break(self, text):
+ '''
+ 将html标记清除,并替换成对应的换行符
+ '''
+ symbol = ('
', '
', ' ') + for s in symbol: + text = re.sub(s, '', text) + # 替换为换行 + text = re.sub(r'<.+[/]{1}>|<[/]{1}.>|', '
', ' ') + for s in symbol: + text = re.sub(s, '', text, flags=re.M|re.I|re.S) + # 替换为换行 + text = re.sub(r'<.+[/]{1}>|<[/]{1}.>|