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}.>|
', r'\n', text, flags=re.M|re.I|re.S) + return text.strip() + + def write_excel(self, file_name): + ''' + 将构造的字典树写入Excel文件中 + ''' + workbook = xlwt.Workbook(encoding='utf-8') + size = 100 + # 标题 + titles = ((u'模块', 35), (u'用例标题', 50), (u'重要性', 25), (u'执行方式', 27), (u'摘要', 80), (u'前提', 80), (u'步骤', 160), (u'结果', 160)) + result = {} + result['module'] = 0 + result['case'] = 0 + for sheet in self.__xml_tree: + sheet_name = sheet.get('sheet_name') + # 插入sheet名称 + worksheet = workbook.add_sheet(sheet_name) + col = 0 + row = 1 + end = 0 + title_row = 1 + title_end = 0 + # 写入标题 + for t, w in titles: + width = w * size + worksheet.write(0, col, t, self.set_style(11)) + worksheet.col(col).width = width + col = col + 1 + # 写模块 + for module in sheet.get(sheet_name): + # 写模块名 + end = row + int(module.get('row')) - 1 + worksheet.write_merge(row, end, 0, 0, module.get('module'), self.set_style(12)) + row = end + 1 # 更新模块列 + center = self.set_style() + left = self.set_style(align='left') + # 写用例 + for case in module.get('testcase'): + title_end = int(case.get('row')) + title_row - 1 + worksheet.write_merge(title_row, title_end, 1, 1, case.get('casename'), center) + worksheet.write_merge(title_row, title_end, 2, 2, case.get('importance'), center) + worksheet.write_merge(title_row, title_end, 3, 3, case.get('execution_type'), center) + summary = case.get('summary') + worksheet.write_merge(title_row, title_end, 4, 4, self.__auto_break(summary), left) + prec = case.get('preconditions') + worksheet.write_merge(title_row, title_end, 5, 5, self.__auto_break(prec), left) + # 步骤 + step_row = title_row + for step in case.get('steps'): + actions = step.get('actions') + worksheet.write(step_row, 6, self.__auto_break(actions), left) + results = step.get('expectedresults') + worksheet.write(step_row, 7, self.__auto_break(results), left) + step_row = step_row + 1 + title_row = title_end + 1 + # 用例数+1 + result['case'] = result['case'] + 1 + + # 模块数+1 + result['module'] = result['module'] + 1 + # 写入Excel + workbook.save(file_name) + return result + + + +if __name__ == "__main__": + print(u'xml转Excel助手,用于TestLink导出测试案例转换') \ No newline at end of file diff --git a/Testlink转换工具/XmlToExcel2/check.py b/Testlink转换工具/XmlToExcel2/check.py new file mode 100644 index 0000000..c52fbd2 --- /dev/null +++ b/Testlink转换工具/XmlToExcel2/check.py @@ -0,0 +1,39 @@ +# *-* coding: utf-8 *-* +import os +import sys + + +# 用于环境检查 +# 检查Python版本 +# 检查Python模块,需要xlrd +def check_env(): + # 检查Python版本 + print(sys.version_info) + if sys.version_info > (2, 9): + print('***************************') + print('ERROR: Python版本高于2.x') + 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 xlwt + except: + print('缺少xlwt模块,将自动安装...') + # 执行安装 + p = os.popen('pip install xlwt') + print(p.read()) + import xlrd + finally: + print('***************************') + print('环境配置正常,可以运行转换工具') + print('***************************') + +if __name__ == "__main__": + check_env() + diff --git a/Testlink转换工具/XmlToExcel2/converter.py b/Testlink转换工具/XmlToExcel2/converter.py new file mode 100644 index 0000000..7f9d38f --- /dev/null +++ b/Testlink转换工具/XmlToExcel2/converter.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +from XmlToExcel import XmlToExcel +import sys +import os +import time + +# 定义后缀过滤列表 +filter_list = ['.xml'] + +# 说明:检查output文件夹是否存在,不存在则创建 +# 参数:无 +# 返回值:输出路径 +def get_output_dir(): + output = os.path.join(os.getcwd(), 'output') + if not os.path.exists(output): + os.mkdir(output) + return output + + +# 说明:遍历指定文件夹扫描xml文件 +# 参数:无 +# 返回值:输出文件夹下所有xml文件路径列表 +def get_input_xml(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(): + xmls = [] + input_dir = '' + # 未传入参数,使用默认设置 + if len(sys.argv) == 1: + input_dir = os.path.join(os.getcwd(), 'input') + xmls = get_input_xml(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] + # 判断是否是xml + if ext not in filter_list: + print(u'ERROR:请传入正确的xml文件') + return False + xmls.append(input_dir) + elif os.path.exists(input_dir): # 如果是文件夹 + xmls = get_input_xml(input_dir) + return xmls, input_dir + + +# 说明:将指定xml转换为Excel文件 +# 参数:无 +# 返回值:成功返回True +def converter(): + # 获取命令行参数 + xmls, input_dir = get_command() + # 校验文件输入 + if len(xmls) == 0: + print(u'ERROR: 未发现输入的xml文件') + return False + print('--------------------------------') + # print(u'INFO: 当前输入文件为:{}'.format(input_dir)) + output = get_output_dir() + # 遍历文件输出 + print(u'INFO: 开始执行xml转Excel...') + print('--------------------------------') + # 实例化 + xm = XmlToExcel() + # 返回转换结果 + result = xm.read_xml(xmls) + file_name = time.strftime(r'%Y%m%d%H%M%S', time.localtime()) + '_Example_Testsuite_Default.xls' + excel = os.path.join(output, file_name) + # 写入Excel文件 + res = xm.write_excel(excel) + + print(u'成功转换文件数:\t{}'.format(result['success'])) + print(u'失败转换文件数:\t{}'.format(result['fail'])) + print(u'模块总数为:\t{}'.format(res['module'])) + print(u'案例总数为:\t{}'.format(res['case'])) + print('------------------------') + print(u'INFO: 全部转换成功,已输出到文件:{}'.format(excel)) + return True + + +if __name__ == "__main__": + # 开始转换 + converter() \ No newline at end of file diff --git a/Testlink转换工具/XmlToExcel2/readme.txt b/Testlink转换工具/XmlToExcel2/readme.txt new file mode 100644 index 0000000..7cb6d20 --- /dev/null +++ b/Testlink转换工具/XmlToExcel2/readme.txt @@ -0,0 +1,16 @@ +Ҫ +1Python 2.x +2ģxlwt +ʹ python check.py ɰ汾ģ鰲װ + + +в +1python converter.py +Xmlļinputļ£Excelļoutputļ£Զ +2python converter.py ļ· +ָļУļ·пո񣩣xmlļoutputļ +3python converter.py xmlļ· +ָļExcelļoutputļ + +ļΪ +ʱ+_Example_Testsuite_Default.xls \ No newline at end of file diff --git a/Testlink转换工具/XmlToExcel3/XmlToExcel.py b/Testlink转换工具/XmlToExcel3/XmlToExcel.py new file mode 100644 index 0000000..0ad9e96 --- /dev/null +++ b/Testlink转换工具/XmlToExcel3/XmlToExcel.py @@ -0,0 +1,282 @@ +# -*- 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 +import json + +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 '低' + elif num == '2': + return '中' + elif num == '3': + return '高' + return num + + + def __get_exec_type(self, num): + ''' + 执行方式序号转换成对应的中文 + ''' + if num == '1': + return '手工' + elif num == '2': + return '自动的' + 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 = [] + for testsuite in root.getElementsByTagName('testsuite'): + module_tree = {} + # 模块名 + module = testsuite.getAttribute('name') + module_tree['module'] = module + module_tree['testcase'] = [] + module_row = 0 + for case in testsuite.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 + suites.append(module_tree) + return suites + + 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("xml文件有误, 请检查testsuite的name属性, 不能为空."); + 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 = '宋体' + # 对齐方式 + 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, flags=re.M|re.I|re.S) + # 替换为换行 + text = re.sub(r'<.+[/]{1}>|<[/]{1}.>|
', r'\n', text, flags=re.M|re.I|re.S) + return text.strip() + + def write_excel(self, file_name): + ''' + 将构造的字典树写入Excel文件中 + ''' + workbook = xlwt.Workbook(encoding='utf-8') + size = 100 + # 标题 + titles = (('模块', 35), ('用例标题', 50), ('重要性', 25), ('执行方式', 27), ('摘要', 80), ('前提', 80), ('步骤', 160), ('结果', 160)) + result = {} + result['module'] = 0 + result['case'] = 0 + for sheet in self.__xml_tree: + sheet_name = sheet.get('sheet_name') + # 插入sheet名称 + worksheet = workbook.add_sheet(sheet_name) + col = 0 + row = 1 + end = 0 + title_row = 1 + title_end = 0 + # 写入标题 + for t, w in titles: + width = w * size + worksheet.write(0, col, t, self.set_style(11)) + worksheet.col(col).width = width + col = col + 1 + # 写模块 + for module in sheet.get(sheet_name): + # 写模块名 + end = row + int(module.get('row')) - 1 + worksheet.write_merge(row, end, 0, 0, module.get('module'), self.set_style(12)) + row = end + 1 # 更新模块列 + center = self.set_style() + left = self.set_style(align='left') + # 写用例 + for case in module.get('testcase'): + title_end = int(case.get('row')) + title_row - 1 + worksheet.write_merge(title_row, title_end, 1, 1, case.get('casename'), center) + worksheet.write_merge(title_row, title_end, 2, 2, case.get('importance'), center) + worksheet.write_merge(title_row, title_end, 3, 3, case.get('execution_type'), center) + summary = case.get('summary') + worksheet.write_merge(title_row, title_end, 4, 4, self.__auto_break(summary), left) + prec = case.get('preconditions') + worksheet.write_merge(title_row, title_end, 5, 5, self.__auto_break(prec), left) + # 步骤 + step_row = title_row + for step in case.get('steps'): + actions = step.get('actions') + worksheet.write(step_row, 6, self.__auto_break(actions), left) + results = step.get('expectedresults') + worksheet.write(step_row, 7, self.__auto_break(results), left) + step_row = step_row + 1 + title_row = title_end + 1 + # 用例数+1 + result['case'] = result['case'] + 1 + + # 模块数+1 + result['module'] = result['module'] + 1 + # 写入Excel + workbook.save(file_name) + return result + + + +if __name__ == "__main__": + file = [os.path.join(os.getcwd(), 'test.xml')] + excel = os.path.join(os.getcwd(), 'test.xls') + dom = XmlToExcel() + dom.read_xml(file) + dom.write_excel(excel) \ No newline at end of file diff --git a/Testlink转换工具/XmlToExcel3/check.py b/Testlink转换工具/XmlToExcel3/check.py new file mode 100644 index 0000000..3e757e0 --- /dev/null +++ b/Testlink转换工具/XmlToExcel3/check.py @@ -0,0 +1,38 @@ +# *-* coding: utf-8 *-* +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 xlwt + except: + print('缺少xlwt模块,将自动安装...') + # 执行安装 + p = os.popen('pip install xlwt') + print(p.read()) + import xlrd + finally: + print('***************************') + print('环境配置正常,可以运行转换工具') + print('***************************') + +if __name__ == "__main__": + check_env() + diff --git a/Testlink转换工具/XmlToExcel3/converter.py b/Testlink转换工具/XmlToExcel3/converter.py new file mode 100644 index 0000000..1b7ddb6 --- /dev/null +++ b/Testlink转换工具/XmlToExcel3/converter.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +from XmlToExcel import XmlToExcel +import sys +import os +import time + +# 定义后缀过滤列表 +filter_list = ['.xml'] + +# 说明:检查output文件夹是否存在,不存在则创建 +# 参数:无 +# 返回值:输出路径 +def get_output_dir(): + output = os.path.join(os.getcwd(), 'output') + if not os.path.exists(output): + os.mkdir(output) + return output + + +# 说明:遍历指定文件夹扫描xml文件 +# 参数:无 +# 返回值:输出文件夹下所有xml文件路径列表 +def get_input_xml(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(): + xmls = [] + input_dir = '' + # 未传入参数,使用默认设置 + if len(sys.argv) == 1: + input_dir = os.path.join(os.getcwd(), 'input') + xmls = get_input_xml(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] + # 判断是否是xml + if ext not in filter_list: + print(u'ERROR:请传入正确的xml文件') + return False + xmls.append(input_dir) + elif os.path.exists(input_dir): # 如果是文件夹 + xmls = get_input_xml(input_dir) + return xmls, input_dir + + +# 说明:将指定xml转换为Excel文件 +# 参数:无 +# 返回值:成功返回True +def converter(): + # 获取命令行参数 + xmls, input_dir = get_command() + # 校验文件输入 + if len(xmls) == 0: + print('ERROR: 未发现输入的xml文件') + return False + print('--------------------------------') + print('INFO: 当前输入文件为:', input_dir) + output = get_output_dir() + # 遍历文件输出 + print('INFO: 开始执行xml转Excel...') + print('--------------------------------') + # 实例化 + xm = XmlToExcel() + # 返回转换结果 + result = xm.read_xml(xmls) + file_name = time.strftime(r'%Y%m%d%H%M%S', time.localtime()) + '_Example_Testsuite_Default.xls' + excel = os.path.join(output, file_name) + # 写入Excel文件 + res = xm.write_excel(excel) + + print('成功转换文件数:\t', result['success']) + print('失败转换文件数:\t', result['fail']) + print('模块总数为:\t', res['module']) + print('案例总数为:\t', res['case']) + print('------------------------') + print('INFO: 全部转换成功,已输出到文件:', excel) + return True + + +if __name__ == "__main__": + # 开始转换 + converter() \ No newline at end of file diff --git a/Testlink转换工具/XmlToExcel3/readme.txt b/Testlink转换工具/XmlToExcel3/readme.txt new file mode 100644 index 0000000..6d2b6e3 --- /dev/null +++ b/Testlink转换工具/XmlToExcel3/readme.txt @@ -0,0 +1,16 @@ +Ҫ +1Python 3.x +2ģxlwt +ʹ python check.py ɰ汾ģ鰲װ + + +в +1python converter.py +Xmlļinputļ£Excelļoutputļ£Զ +2python converter.py ļ· +ָļУļ·пո񣩣xmlļoutputļ +3python converter.py xmlļ· +ָļExcelļoutputļ + +ļΪ +ʱ+_Example_Testsuite_Default.xls \ No newline at end of file