diff --git a/Changes.txt b/Changes.txt new file mode 100644 index 0000000..522ef0c --- /dev/null +++ b/Changes.txt @@ -0,0 +1,63 @@ + changes log +====================================== +1.1.0 2021-12-13 + +修改tox和主程序名称 + +[mh] +------------------------------ +1.1.0 2021-01-16 + +兼容py2.7 + +[mh] +------------------------------ +1.1.0 2021-01-15 + +更改方法,pandas太庞大了,功能严重冗余,直接用xlrd来做 + +[mh] +------------------------------ +1.0.1 2021-01-11 + +基础功能测试用例中添加子模块的注释信息 + +[mh] +------------------------------ +1.0.1 2020-11-27 + +修改编译,支持打包 + +[mh] + +------------------------------ +1.0.0 2020-11-05 + +1.format格式 +2.添加异常测试的实现 +3.所有预期功能全部完成 + +[mh] +------------------------------ +0.0.1 2020-11-05 + +1.修改程序,能够处理多个文件 +2.增加多个模板,用来处理多个不同的sheet + +[mh] +------------------------------ +0.0.1 2020-11-02 + +1.使用pandas+jinja2来做 +2.只针对基础功能测试的sheet页,生成1个feature文件 + +[mh] +------------------------------ +0.0.1 2020-10-28 + +1.开始规划,先调研一下怎么做 +2.提交基础框架 + + COMMON-6503 【测试用例】做个程序将Excel转为feature,方面维护 + +[mh] diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..650a9fe --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include README.txt +include requirements.txt tox.ini +recursive-include test *.txt *.py +recursive-include *.txt *.py *.tmpl +recursive-include bin *.* diff --git a/README.md b/README.md index 4d55018..df4ccf7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # excel2feature -将excel转换为behave的features文件 \ No newline at end of file +将excel转换为behave的features文件 + + +使用方法: +1. 安装python3.7 开发环境 +2.用tox构建环境 + tox -e devenv +3.将修改后的excel文件放到excel_files目录中 +4.运行程序 + devenv\Scripts\activate + python bin\e2f.py +5.查看output目录生成的feature文件 +6.拿生成的feature放到项目中运行一下,确认生成的Excel文件符合预期 diff --git a/bin/_load.py b/bin/_load.py new file mode 100644 index 0000000..d04f15f --- /dev/null +++ b/bin/_load.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +import os +import sys +import site + +# set path +py_dir = os.path.dirname(os.path.realpath(__file__)) + +parent_dir = os.path.dirname(py_dir) + +# print parent_dir +# if os.path.isdir(os.path.join(parent_dir, "delegate")): + # sys.path.append(parent_dir) + +libdir = os.path.join(parent_dir, "lib") +# print libdir +if os.path.isdir(libdir): + old_len = len(sys.path) + new_sys_path = [] + site.addsitedir(libdir) # @UndefinedVariable + for item in sys.path[old_len:]: + new_sys_path.append(item) + sys.path.remove(item) + sys.path[:0] = new_sys_path + +# set cwd +os.chdir(parent_dir) diff --git a/bin/e2f.py b/bin/e2f.py new file mode 100644 index 0000000..2e775b5 --- /dev/null +++ b/bin/e2f.py @@ -0,0 +1,180 @@ +# -*- coding:utf-8 -*- +from __future__ import unicode_literals +import os +import io +import _load +import shutil +import time +import xlrd + +from fnmatch import fnmatch +from jinja2 import Environment, PackageLoader +#处理py2的编码和字典顺序的问题 +#from __future__ import unicode_literals --必须放在第一个 +from collections import OrderedDict + + +class xl2fe: + + def __init__(self): + HERE = os.path.dirname(__file__) + TOP = os.path.join(HERE, "..") + self.templates_dir = os.path.join(TOP, "templates") + self.output_dir = os.path.join(TOP, "output") + if os.path.exists(self.output_dir): + shutil.rmtree(self.output_dir) + time.sleep(2) + os.mkdir(self.output_dir) + self.excel_files_dir = os.path.join(TOP, "excel_files") + self.xlsx_sheets = {"封面": "00_home_index.feature", + "统计表": "01_statistics.feature", + "基础功能测试用例": "02_function.feature", + "异常测试": "03_abnormality_test.feature", + "性能测试": "04_performance.feature" + } + + def read_xlsx(self): + """ + 指定的sheet页,对内容进行处理 + """ + #获取所有的数据 + work_book = xlrd.open_workbook(self.xlsx_file_path) + #为了让py2字典的顺序与py3一致 + all_data = OrderedDict() + for i, sheet_obj in enumerate(work_book.sheets()): + all_data[sheet_obj.name] = [sheet_obj.row_values(row) + for row in range(sheet_obj.nrows)] + #按sheet页处理 + for sheet_name, self.feature_name in self.xlsx_sheets.items(): + datas = all_data[sheet_name] + # 处理数据 + self.context = OrderedDict() + self.context = xl2fe.get_feature_data(self, datas, sheet_name) + # 渲染模板 + xl2fe.feature_tpl(self) + + def get_feature_data(self, datas, sheet_name): + """ + 根据sheet_name 处理数据 + """ + context_temp = OrderedDict() + + if sheet_name == "封面": + context_temp['project'] = self.project + context_temp['sheet_name'] = sheet_name + # 处理更新记录 + Scenario_table = [] + lines = datas[4:] + for line in lines: + cells = line + # 处理换行 + for index, cell in enumerate(cells): + #因为py2的编码问题,不能判断str + #isinstance(cell,str) + if not isinstance(cell,(int,float)): + cells[index] = cell.replace('\n', '\\n') + Scenario_table.append(cells) + context_temp['Scenario_table'] = Scenario_table + + elif sheet_name == "统计表": + context_temp['project'] = self.project + context_temp['sheet_name'] = sheet_name + + elif sheet_name == "基础功能测试用例": + context_temp['project'] = self.project + context_temp['sheet_name'] = sheet_name + # 处理基础测试用例中的数据 + Scenario_table = OrderedDict() + lines = datas[2:] + for line in lines: + cells = line[0:9] + #补全合并单元格的信息 + #模块 + if cells[0]: + model = cells[0] + else: + cells[0] = model + #子模块 + if cells[1]: + sub_model = cells[1] + else: + cells[1] = sub_model + #处理编号 + if '-ST-' in cells[2]: + cells[2] = 'NUM' + # 处理换行 + for index, cell in enumerate(cells): + if not isinstance(cell,(int,float)): + cells[index] = cell.replace('\n', '\\n') + #以模块为单位存储 + if model not in list(Scenario_table.keys()): + Scenario_table[model] = [] + Scenario_table[model].append(cells) + context_temp['Scenario_table'] = Scenario_table + + elif sheet_name == "异常测试": + context_temp['project'] = self.project + context_temp['sheet_name'] = sheet_name + # 处理更新记录 + Scenario_table = [] + lines = datas[4:] + for line in lines: + cells = line[0:8] + cells[0] = 'NUM' + # 处理换行 + for index, cell in enumerate(cells): + if not isinstance(cell,(int,float)): + cells[index] = cell.replace('\n', '\\n') + Scenario_table.append(cells) + context_temp['Scenario_table'] = Scenario_table + + elif sheet_name == "性能测试": + context_temp['project'] = self.project + context_temp['sheet_name'] = sheet_name + + return context_temp + + def feature_tpl(self): + """ + 拿处理后的数据来渲染指定的模板 + """ + # 读取模板 + tpl = os.path.join(self.templates_dir, self.feature_name + ".j2") + tpl_data = io.open(tpl, encoding="utf-8").read() + # 渲染模板 + env = Environment() + text = env.from_string(tpl_data).render(self.context) + # 保存文件 + xl2fe.save_feature(self, text) + + def save_feature(self, text): + """ + 保存渲染好的模板为feature文件 + """ + #为了解决windows换行符的问题转为二进制,主要是由于py2中open不支持newline参数 + #py2没有bytes()函数 + #text_bytes = bytes(text,'utf-8') + text_bytes = text.encode('utf-8') + feature_path = os.path.join(self.project_dir, self.feature_name) + # 写入文件 + with open(feature_path, 'wb+') as fp: + fp.write(text_bytes) + + def main(self): + xlsx_files = os.listdir(self.excel_files_dir) + for xlsx_file in xlsx_files: + # 排除掉非xlsx结尾的文件 + if not fnmatch(xlsx_file, "*.xlsx"): + continue + self.project = xlsx_file.split('_')[0] + self.xlsx_file_path = os.path.join(self.excel_files_dir, xlsx_file) + # 按项目存放 + self.project_dir = os.path.join(self.output_dir, self.project) + os.mkdir(self.project_dir) + + xl2fe.read_xlsx(self) + + +if __name__ == '__main__': + test_env = xl2fe() + test_env.main() diff --git a/excel_files/.gitkeep b/excel_files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/release.py b/release.py new file mode 100644 index 0000000..888f75e --- /dev/null +++ b/release.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + create zip 安装包 +""" + +import logging +import os.path +import shutil +import sys + + +def _copytree(src, dst, ignore=None): + + names = os.listdir(src) + if ignore is not None: + ignored_names = ignore(src, names) + else: + ignored_names = set() + try: + os.makedirs(dst) + except Exception: + pass + errors = [] + for name in names: + if name in ignored_names: + continue + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if os.path.isdir(srcname): + shutil.copytree(srcname, dstname, ignore=ignore) + else: + # Will raise a SpecialFileError for unsupported file types + shutil.copy2(srcname, dstname) + # catch the Error from the recursive copytree so that we can + # continue with other files + except shutil.Error as err: + errors.extend(err.args[0]) + except EnvironmentError as why: + errors.append((srcname, dstname, str(why))) + try: + shutil.copystat(src, dst) + except OSError as why: + if WindowsError is not None and isinstance(why, WindowsError): + # Copying file access times may fail on Windows + pass + else: + errors.extend((src, dst, str(why))) + if errors: + raise shutil.Error(errors) + + +def _zip_file(target_dir): + root_dir = os.path.dirname(target_dir) + os.chdir(root_dir) + shutil.make_archive(os.path.basename(target_dir), format="gztar", + base_dir=os.path.basename(target_dir)) + + +def _strip_py(py_dir): + for base, dirs, files in os.walk(py_dir): + for name in files: + if name.endswith('.py'): + path = os.path.join(base, name) + logging.debug("Deleting %s", path) + os.unlink(path) + + +def main(): + # src_dir = sys.argv[1] + site_pacakge_dir = sys.argv[2] + target_dir = sys.argv[3] + + top_dir = sys.argv[4] + + shutil.rmtree(target_dir, ignore_errors=True) + os.makedirs(target_dir) + + for dir in ("bin", "excel_files", "templates"): + _copytree(os.path.join(top_dir, dir), + os.path.join(target_dir, dir)) + + shutil.copy2(os.path.join(top_dir, "README.txt"), + os.path.join(target_dir, "README.txt")) + + target_lib_dir = os.path.join(target_dir, "lib") + _copytree(site_pacakge_dir, target_lib_dir) + + _zip_file(target_dir) + + print("") + print("output dir %s" %(target_dir)) + +if __name__ == '__main__': + try: + main() + except Exception: + logging.exception("main except") + sys.exit(1) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..14bd30b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +MarkupSafe==2.0.1; python_version > "2.7" +MarkupSafe==1.1.1; python_version <= "2.7" +xlrd==1.2.0 +Jinja2==2.11.2 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6397765 --- /dev/null +++ b/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup, find_packages + + +install_requires=[] + +for line in open('requirements.txt'): + install_requires.append(line.strip()) + +setup(name='excel2feature', + version='0.0.1', + description='', + long_description="""\ +""", + # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers # nopep8 + classifiers=[ + "Programming Language :: Python", + ], + keywords='', + author='mh', + author_email='menghan@lzy.com', + url='', + license='GPL', + packages=find_packages(exclude=["ez_setup","test.*", "test"]), + namespace_packages=[], + include_package_data=True, + test_suite='nose.collector', + zip_safe=False, + install_requires=install_requires, + entry_points=""" + # -*- Entry points: -*- + """, + ) diff --git a/templates/00_home_index.feature.j2 b/templates/00_home_index.feature.j2 new file mode 100644 index 0000000..70f93d3 --- /dev/null +++ b/templates/00_home_index.feature.j2 @@ -0,0 +1,32 @@ +# encoding:UTF-8 +@{{ project }} +@excel +Feature: {{ sheet_name }} + + Background: + Given I active "{{ sheet_name }}" + + Scenario: 更新记录 + Then I set "A1" value + """ + {{ project | upper }}测试用例 + """ + And I load the table + | 版本号| 修改日期 | 修改内容 | 修订人 | + {%- for lines in Scenario_table %} + | {{ lines |join(' | ') }} | + {%- endfor %} + + + Scenario: 调整样式 + # 指定区内自动换行 + Then I auto set wrap start at "A5" + """ + { + "horizontal": "left", + "vertical": "center" + } + """ + #使用的范围加上边框 + Then I auto set border + diff --git a/templates/01_statistics.feature.j2 b/templates/01_statistics.feature.j2 new file mode 100644 index 0000000..3d5f1a3 --- /dev/null +++ b/templates/01_statistics.feature.j2 @@ -0,0 +1,10 @@ +# encoding:UTF-8 +@{{ project }} +@excel +Feature: {{ sheet_name }} + + Background: + Given I active "{{ sheet_name }}" + + Scenario: 统计结果 + diff --git a/templates/02_function.feature.j2 b/templates/02_function.feature.j2 new file mode 100644 index 0000000..207b256 --- /dev/null +++ b/templates/02_function.feature.j2 @@ -0,0 +1,46 @@ +# encoding:UTF-8 +@{{ project }} +@excel +Feature: {{ sheet_name }} + + Background: + Given I active "{{ sheet_name }}" + +{#- 以模块为单位,一个模块一个Scenario #} +{% for model,table in Scenario_table.items() %} + Scenario: {{ model }} + Then I load the table + | 功能模块 | 子模块 | 用例编号 | 用例名称 | 重要级别 | 操作步骤 | 预期结果 | 更新版本 | 备注 | 测试结果 | 测试人员 | 测试时间 | + {#- 根据子模块,添加注释信息,起到分隔的作用 #} + {%- set sub_model = namespace(value=None) %} + {%- for lines in table %} + {%- if sub_model.value != lines[1] %} + {%- set sub_model.value = lines[1] %} + #{{sub_model.value}} + {%- endif %} + | {{ lines |join(' | ') }} | | | | + {%- endfor %} +{% endfor %} + + + + Scenario: 调整样式 + #自动生成用例编号 + Then I set case_number on "C" + """ + {{ project | upper }}-ST- + """ + # 指定列自动合并单元格 + Then I auto_merge_cells "A" start at "3" + Then I auto_merge_cells "B" start at "3" + # 指定区内自动换行 + Then I auto set wrap start at "D3" + """ + { + "horizontal": "left", + "vertical": "center" + } + """ + #使用的范围加上边框 + Then I auto set border + diff --git a/templates/03_abnormality_test.feature.j2 b/templates/03_abnormality_test.feature.j2 new file mode 100644 index 0000000..97b0b15 --- /dev/null +++ b/templates/03_abnormality_test.feature.j2 @@ -0,0 +1,30 @@ +# encoding:UTF-8 +@{{ project }} +@excel +Feature: {{ sheet_name }} + + Background: + Given I active "{{ sheet_name }}" + + Scenario: 添加异常测试 + Then I load the table + | 序号 | JIRA号 | 版本号 | 问题描述 | 操作步骤 | 测试数据 | 预期结果 | 是否自动化 | + {%- for lines in Scenario_table %} + | {{ lines |join(' | ') }} | + {%- endfor %} + + + Scenario: 调整样式 + #自动生成用例编号 + Then I set case_number on "A" + #自动换行并居中 + Then I auto set wrap start at "D5" + """ + { + "horizontal": "left", + "vertical": "center" + } + """ + #使用的范围加上边框 + Then I auto set border + diff --git a/templates/04_performance.feature.j2 b/templates/04_performance.feature.j2 new file mode 100644 index 0000000..7c4bbe5 --- /dev/null +++ b/templates/04_performance.feature.j2 @@ -0,0 +1,10 @@ +# encoding:UTF-8 +@{{ project }} +@excel +Feature: {{ sheet_name }} + + Background: + Given I active "{{ sheet_name }}" + + Scenario: 性能测试 + diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..56c5bd1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,44 @@ +[tox] +envlist = devenv +minversion = 1.6 +skipsdist = False + +[testenv] +install_command = pip install --force-reinstall -U {opts} {packages} +setenv = VIRTUAL_ENV={envdir} + NOSE_WITH_COVERAGE=1 + NOSE_COVER_BRANCHES=1 +deps = + -r{toxinidir}/requirements.txt + + +[testenv:devenv] +envdir = devenv +basepython = python3 +usedevelop = True +commands = + {envpython} {toxinidir}/bin/e2f.py + +[testenv:devenv2] +envdir = devenv2 +basepython = python2.7 +usedevelop = True + + +[testenv:py3-release] +basepython = python3 +deps = + -r{toxinidir}/requirements.txt + +commands = + {envpython} {toxinidir}/release.py {envdir} {envsitepackagesdir} {toxinidir}/build/excel2feature {toxinidir} + + +[testenv:py27-release] +basepython = python2.7 +deps = + -r{toxinidir}/requirements.txt + +commands = + {envpython} {toxinidir}/release.py {envdir} {envsitepackagesdir} {toxinidir}/build/excel2feature {toxinidir} +