XMLの内容をExcelで出力する

181
NO IMAGE

背景

関わっているプロジェクトでは、CodeBuildを使ってJunitテストの実行結果をレポートとして出力しています。
しかし、XML形式であり、ファイルがテストクラスごとに作成されるため、一覧性が悪いという問題があります。
ブラウザ上で結果の一覧を見ることもできますが、Stacktraceなど詳細を確認したい場合には、一覧からリンクで詳細を開かないといけません。

この問題を解決するために、出力されたXMLファイルをExcelファイルに変換するpythonプログラムを作成しました。

XMLをExcelに変換する

この記事では、レポートファイルの収集方法などは省略し、XMLの内容を抽出してExcelとして出力する部分に焦点を当てます。

変換元のファイル

変換元のXMLファイルはこのような内容になっています。

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="xxx.yyy.zzz.SampleTest" tests="3" skipped="0" failures="2" errors="0" timestamp="2024-05-20T22:51:25" hostname="abcdefg" time="0.077">
  <properties/>
  <testcase name="test1()" classname="xxx.yyy.zzz.SampleTest" time="0.024"/>
  <testcase name="test2()" classname="xxx.yyy.zzz.SampleTest" time="0.021">
    <failure message="エラーメッセージ" type="発生したException">スタックトレースの内容</failure>
  </testcase>
  <testcase name="test3()" classname="xxx.yyy.zzz.SampleTest" time="0.032">
    <failure message="エラーメッセージ" type="発生したException">スタックトレースの内容</failure>
  </testcase>
  <system-out><![CDATA[system-outの内容]]></system-out>
  <system-err><![CDATA[system-erroの内容]]></system-err>
</testsuite>

xmltodictでXMLを辞書に変換する

まず、xmltodictをインストールします。

pip install xmltodict

xmltodictを使ってXMLを辞書に変換します。

import xmltodict

with open('XMLファイルのパス') as f:
  xml = f.read()
  dic = xmltodict.parse(xml)
  print(dic)

変換してできた辞書の内容はこのようになります。

{
  'testsuite': {
    '@name': 'xxx.yyy.zzz.SampleTest',
    '@tests': '3',
    '@skipped': '0',
    '@failures': '2',
    '@errors': '0',
    '@timestamp': '2024-05-20T22: 51: 25',
    '@hostname': 'abcdefg',
    '@time': '0.077',
    'properties': None,
    'testcase': [
      {
        '@name': 'test1()',
        '@classname': 'xxx.yyy.zzz.SampleTest',
        '@time': '0.024'
      },
      {
        '@name': 'test2()',
        '@classname': 'xxx.yyy.zzz.SampleTest',
        '@time': '0.021',
        'failure': {
          '@message': 'エラーメッセージが出力される',
          '@type': '発生したException',
          '#text': 'スタックトレースの内容'
        }
      },
      {
        '@name': 'test3()',
        '@classname': 'xxx.yyy.zzz.SampleTest',
        '@time': '0.032',
        'failure': {
          '@message': 'エラーメッセージが出力される',
          '@type': '発生したException',
          '#text': 'スタックトレースの内容'
        }
      }
    ],
    'system-out': 'system-outの内容',
    'system-err': 'system-erroの内容'
  }
}

pandasでExcelに変換する

今回はpandasを使ってExcel出力するため、pandasをインストールしておきます。

pip install pandas

なお、pandasでxlsx形式で取得するときにはopenpyxlが使われるため、これもインストールしておきます。

pip install openpyxl

Excel出力するために、testsuite.testcaseから以下の内容を抽出し、行データ(配列)に変換します。

  • @classname
  • @name
  • @time
  • failure.#text
  • error.#text
testcases = [
  [
    testcase['@classname'],
    testcase['@name'],
    testcase['@time'],
    testcase['failure']['#text'] if 'failure' in testcase else None,
    testcase['error']['#text'] if 'error' in testcase else None
  ] for testcase in dic['testsuite']['testcase']
]

できた配列をpandasのDataFrameオブジェクトに変換します。

import pandas as pd

df = pd.DataFrame(testcases)
print(df)

以下がDataFrameオブジェクトの内容です。

                        0        1      2            3     4
0  xxx.yyy.zzz.SampleTest  test1()  0.024         None  None
1  xxx.yyy.zzz.SampleTest  test2()  0.021  スタックトレースの内容  None
2  xxx.yyy.zzz.SampleTest  test3()  0.032  スタックトレースの内容  None

この内容がExcelに出力されますが、行ヘッダ、列ヘッダがわかりづらいため、以下のように修正します。

df = pd.DataFrame(testcases,
                  # 列ヘッダ:'classname'、'name'、'time'、'failure'、'error'にする
                  columns=['classname', 'name', 'time', 'failure', 'error'])
# 行ヘッダ:先頭を1にする
df.index += 1
print(df)

最終的な出力結果はこのようになります。

                classname     name   time      failure error
1  xxx.yyy.zzz.SampleTest  test1()  0.024         None  None
2  xxx.yyy.zzz.SampleTest  test2()  0.021  スタックトレースの内容  None
3  xxx.yyy.zzz.SampleTest  test3()  0.032  スタックトレースの内容  None

最後にExcelファイルに出力します。

df.to_excel('出力先ファイルのパス')

補足

今回は1ファイルの内容だけをExcel出力しましたが、実際のレポートは複数ファイルになっているため、それをまとめて1つのExcelファイルに出力するには、少し改良が必要となります。