Skip to content

Instantly share code, notes, and snippets.

@toriwasa
Created November 9, 2017 03:40
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save toriwasa/37c690862ddf67d43cfd3e1af4e40649 to your computer and use it in GitHub Desktop.
Save toriwasa/37c690862ddf67d43cfd3e1af4e40649 to your computer and use it in GitHub Desktop.
Markdown形式で記述したテスト仕様書をExcelに変換するツール
# -*- coding: utf-8 -*-
import re
from pathlib import PurePath
from typing import Any, Dict # NOQA
import fire
import pandas as pd
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.styles.borders import BORDER_THIN
def convert_to_excel(input_path: str, output_sheet: str = 'Sheet1') -> None:
"""Markdown形式で記述したテスト仕様書をExcelに変換するツール
Arguments:
input_path: 変換対象のMarkdownファイルパス
output_sheet: 出力するExcelファイルのシート名。デフォルトはSheet1
Returns:
None
実行するとインプットファイルと同じディレクトリに拡張子がxlsxになったファイルが出力される。
変換するMarkdownは以下の形式で記述する::
# テスト名
## 大項目 e.g. APIインターフェース
### [正常|異常]/中項目 e.g. 異常/パラメータ指定なし
#### 小項目 e.g. パラメータ指定なし
1. 確認手順 e.g. パラメータなしでリクエストを行う
2. 確認手順
* [ ] 想定動作 e.g. パラメータ指定不足エラーが返却される
"""
pure_input_path = PurePath(input_path.replace('\\', '/'))
output_path: str = str(pure_input_path.with_suffix('.xlsx'))
df_header = (
'大項目', '正常系/異常系', '中項目', '小項目', '確認手順', '想定動作',
'確認手順最大文字数', '想定動作最大文字数'
)
# Excelデータ変換用の空データフレーム
df = pd.DataFrame(index=[], columns=df_header)
with open(input_path, 'r', encoding='utf-8') as input_file:
current_item_dict: Dict[str, Any] = {
'大項目': '',
'正常系/異常系': '',
'中項目': '',
'小項目': '',
'確認手順': '',
'想定動作': '',
'確認手順最大文字数': 0,
'想定動作最大文字数': 0
}
for line in input_file:
# Markdownをパースしていく
if re.match(r'[0-9]+\. ', line):
current_item_dict['確認手順'] += line
# 文字数が最大なら更新しておく
if current_item_dict['確認手順最大文字数'] < len(line):
current_item_dict['確認手順最大文字数'] = len(line)
elif line.startswith('* [ ] '):
current_item_dict['想定動作'] += '・' + line[6:]
# 文字数が最大なら更新しておく
if current_item_dict['想定動作最大文字数'] < len(line):
current_item_dict['想定動作最大文字数'] = len(line)
elif current_item_dict['確認手順'] and current_item_dict['想定動作']:
# ここまでの項目をデータフレームに追加
# 確認手順、想定動作については最後の改行コードを除いて追加する
se = pd.Series([
current_item_dict['大項目'],
current_item_dict['正常系/異常系'],
current_item_dict['中項目'],
current_item_dict['小項目'],
current_item_dict['確認手順'][:-1],
current_item_dict['想定動作'][:-1],
current_item_dict['確認手順最大文字数'],
current_item_dict['想定動作最大文字数']
], index=df.columns)
df = df.append(se, ignore_index=True)
# 終わったら初期化
current_item_dict['確認手順'] = ''
current_item_dict['想定動作'] = ''
current_item_dict['確認手順最大文字数'] = 0
current_item_dict['想定動作最大文字数'] = 0
elif line.startswith('## '):
current_item_dict['大項目'] = line[3:-1]
elif line.startswith('### '):
current_item_dict['正常系/異常系'] = line[4:6]
current_item_dict['中項目'] = line[7:-1]
elif line.startswith('#### '):
current_item_dict['小項目'] = line[5:-1]
# ファイル終了時点の最後の項目を追加
if current_item_dict['確認手順'] and current_item_dict['想定動作']:
se = pd.Series([
current_item_dict['大項目'],
current_item_dict['正常系/異常系'],
current_item_dict['中項目'],
current_item_dict['小項目'],
current_item_dict['確認手順'][:-1],
current_item_dict['想定動作'][:-1],
current_item_dict['確認手順最大文字数'],
current_item_dict['想定動作最大文字数']
], index=df.columns)
df = df.append(se, ignore_index=True)
# ここからExcelデータをwriterインスタンスに書き込む
writer = pd.ExcelWriter(output_path)
result_df = df.iloc[:, 0:6]
# 後でマージできるようにマルチインデックス化
result_df = result_df.set_index(['大項目', '正常系/異常系', '中項目', '小項目'])
# 項目番号をカラムとして追加しておく
result_df['No'] = df.index + 1
result_df = result_df.loc[:, ['No', '確認手順', '想定動作']]
result_df.to_excel(writer, sheet_name=output_sheet, merge_cells=True)
# ここからExcelデータの見た目を整えていく
worksheet = writer.sheets[output_sheet]
# 囲む罫線インスタンスを定義
surround_border = Border(
left=Side(style=BORDER_THIN),
right=Side(style=BORDER_THIN),
top=Side(style=BORDER_THIN),
bottom=Side(style=BORDER_THIN)
)
# 記入列のヘッダー追加
header_names = [
'動作結果', '1st合否', '確認日', '確認者',
'動作結果', '2nd合否', '確認日', '確認者'
]
for col, header in zip('HIJKLMNO', header_names):
col_address = col + '1'
worksheet[col_address] = header
worksheet[col_address].border = surround_border
worksheet[col_address].alignment = Alignment(
horizontal='center'
)
# ヘッダーの改行
worksheet['B1'] = '正常系\n/異常系'
# ヘッダーのスタイル
for col in 'ABCDEFGHIJKLMNO':
col_address = col + '1'
worksheet[col_address].font = Font(b=True, color='ffffff')
worksheet[col_address].alignment = Alignment(
vertical='center',
horizontal='center',
wrap_text=True
)
# 1回目結果
if col in 'HIJK':
worksheet[col_address].fill = PatternFill(
patternType='solid',
fgColor='5079bd'
)
# 2回目結果
elif col in 'LMNO':
worksheet[col_address].fill = PatternFill(
patternType='solid',
fgColor='505cbd'
)
else:
worksheet[col_address].fill = PatternFill(
patternType='solid',
fgColor='4f81bd'
)
# 列幅調整
max_length_dict = {}
for col in ['大項目', '中項目', '小項目']:
max_length_dict[col + '最大文字数'] = df[col].map(len).max() * 2
max_length_dict['正常系/異常系最大文字数'] = 9
max_length_dict['No最大文字数'] = 4
max_length_dict['確認手順最大文字数'] = df['確認手順最大文字数'].max()
max_length_dict['想定動作最大文字数'] = df['確認手順最大文字数'].max() * 1.5
data_column_name = [
'大項目', '正常系/異常系', '中項目', '小項目',
'No', '確認手順', '想定動作'
]
for col, k in zip('ABCDEFG', data_column_name):
max_length = max_length_dict[k + '最大文字数']
worksheet.column_dimensions[col].width = max_length
# データセルのスタイル調整
end_row = result_df.loc[:, 'No'][-1]
for n in range(end_row):
row = n + 2
# 位置調整
worksheet['E' + f'{row}'].alignment = Alignment(vertical='top')
worksheet['F' + f'{row}'].alignment = (
Alignment(vertical='top', wrap_text=True)
)
worksheet['G' + f'{row}'].alignment = Alignment(vertical='top')
# 罫線
for col in 'EFGHIJKLMNO':
worksheet[col + f'{row}'].border = surround_border
# 最後にWriteインスタンスを永続化する
writer.save()
if __name__ == '__main__':
fire.Fire(convert_to_excel)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment