はじめに
以前書いたExcel、Excel VBA をGitで管理するという記事が予想以上に反響があったので、テストケースを書いたExcelもGitで管理できないか考えてみたので、記事にしてみました。
システム開発において、すべてのテストをコードで管理するのは難しいのが現状です。
そのため、一部のテストにおいてはExcelやConfluenceで管理されていらっしゃる方も多いのではないでしょうか?
単体テストやE2Eテストなどはコードで管理しているのに、それらを除く一部のテストは別の媒体で管理する。。。なんてのは可能な限り避けたいですよね。
というわけで、テストケースが書かれたExcelもGitで管理できないかを考えてみました。
テストケースを書いたExcelをGitで管理する
ExcelをGitで管理すると、結局バイナリ管理になります。
そこで、前回と同じくpre-commitのライブラリを使用して、コミット前にExcelからテストケースをMarkdownへと抽出するようなロジックを考えました。
なお、Excelからテストケースを抽出するのはpandas と openpyxlのライブラリを使用します。
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: extract-testcases
name: Extract Testcases from Excel files
entry: uv run python .githooks/extract_testcases.py
language: system
files: '\.xlsx$'
pass_filenames: false
always_run: false
stages: [pre-commit]
以下、コミット時に発火するソースコード。
# .githooks/extract_testcases.py
import sys
import os
import subprocess
import pandas as pd
TARGET_EXCEL_FILE = ('.xlsx')
TARGET_DIRS = [
'content/docs/50-dev-docs/tests'
]
IGNORE_COLUMNS = [
'結果',
'実施日',
'実施者',
'補足'
]
def is_target_excel(path):
"""
パスが対象ディレクトリのいずれかの配下にあるExcelファイルかどうかを判定する。
"""
if not path.lower().endswith(TARGET_EXCEL_FILE):
return False
normalized = os.path.normpath(path)
for target_dir in TARGET_DIRS:
normalized_target = os.path.normpath(target_dir)
if normalized.startswith(normalized_target + os.sep) or normalized == normalized_target:
return True
return False
def find_staged_excels():
"""
git のステージに乗っているファイルのうち、
対象ディレクトリ内の拡張子 .xlsx を持つものを返す。
"""
# --diff-filter=ACM: Added, Copied, Modified
# core.quotePath=false で日本語ファイル名を正しく処理
result = subprocess.run(
['git', '-c', 'core.quotePath=false', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
capture_output=True,
text=True,
encoding='utf-8'
)
if result.returncode != 0:
print("Error: git diff failed", file=sys.stderr)
sys.exit(1)
files = result.stdout.strip().split('\n')
if not files or files == ['']:
return []
# 対象ディレクトリ内のExcelファイルのみをフィルタリング
excels = [f for f in files if is_target_excel(f)]
print("Found testcases: ", excels)
return excels
def convert_excel_to_markdown(excel_path):
"""
excel_path を読み込み、同じ名前の .md を生成。
Excelの先頭シートをMarkdown表形式で出力する。
"""
base, _ = os.path.splitext(excel_path)
md_path = base + '.md'
print(f"Converting '{excel_path}' → '{md_path}'")
try:
# Excel の読み込み(先頭シート)
df = pd.read_excel(excel_path, sheet_name=0)
# 不要なカラムを削除(存在しない場合はエラーにしない)
df = df.drop(columns=IGNORE_COLUMNS, errors='ignore')
# Markdown表形式に変換
headers = [str(c) for c in df.columns]
rows = []
for _, row in df.iterrows():
# セル内の改行を<br>に置換
rows.append(["" if pd.isna(v) else str(v).replace('\r\n', '<br>').replace('\n', '<br>').replace('\r', '<br>') for v in row])
# Markdown表の行を構築
lines = []
# ヘッダー行
lines.append("|" + "|".join(headers) + "|")
# 区切り行
lines.append("|" + "|".join(["---"] * len(headers)) + "|")
# データ行
for row in rows:
lines.append("|" + "|".join(row) + "|")
# ファイルに書き出し
content = "\n".join(lines) + "\n"
with open(md_path, 'w', encoding='utf-8') as f:
f.write(content)
subprocess.run(['git', 'add', md_path], check=False)
except Exception as e:
print(f"Failed to convert '{excel_path}': {e}", file=sys.stderr)
sys.exit(1)
def main():
excels = find_staged_excels()
if not excels:
sys.exit(0)
for excel in excels:
convert_excel_to_markdown(excel)
sys.exit(0)
if __name__ == '__main__':
main()
これにより、ExcelからテストケースをMarkdwonで抽出することができました。
抽出したMarkdownを他のドキュメントと合わせてWeb上で参照できるようにすることで、わざわざExcelファイルを開くことなくテストケースの内容を確認できます。
また、テストケースの変更履歴やプルリクを使ったレビューもできるので、良いとは思いませんか?
1点気をつけるべきポイントとしては、あくまでExcelをメインで管理することは変わらないことです。
Markdownのファイルを直接編集しないように注意しましょう。
さいごに
今回、このような仕組みを作ったのは、たまたまテストケースの書かれたExcelをGitで管理しているチームがいたのですが、レビュー時に「テストケースの変更箇所の行番号をPRコメントで大量に残していた」「レビュアーがわざわざExcelファイルを開いて確認していた」のを見て、「手間だなぁ。。。」と思ったからです。
テストケースに限らず、色々なところで応用ができる手段だと思うので、是非みなさん色々と試してみてください。
👋👋👋
