VisualStudioで実行した単体テストの結果の出力(2)
出力ファイルの読める化

2022年7月3日

どもです。
前回の続きです。
前回は、VisualStudio/C#での単体テストの効率化の方法として、batファイルから単体テストを実行する方法について書きました。
しかしその最後で、「完全に改善できていない」とまとめました。
そこで今回のエントリでは、この「完全に改善できていない」点の改善方法について記載します

1.「完全に改善できていない」点

「完全に改善できていない」は、「出力ファイルが読めない/読みにくい」という点です。
単体テストの結果は、XML形式のファイルに出力されます。
そのため、せっかくテスト結果をファイルに出力したのに、ファイルを開いただけでは結果を確認はできません。
開いても結果が確認できない結果ファイル…なんだソレ?です。

2.改善方法

シンプルです。
「読めない」から「読める」にする、です。
最初に書いた通り、出力されるファイルはXML形式です、
これをHTML形式に変換して、ブラウザで表示できるようにします。

3.「読める化」

いきなりですが、XMLをHTMLに変換すると、下記のようになります。
test_result_of_vs_002_001
見た目とか、「センスが無いな」と思わせる部分がたくさんあります。
実際、「センスが無い」ので、当然の結果ですし、当然の感想です。
そこはどーでもいいです。
このように、これまでVisualStudio、またはコマンドライン上でテストを実行しないと確認ができなかったテストの実行結果を、ブラウザで確認ができる、読めるようになりました。

4.どうやった?

pythonでXMLを読み込んで解析し、解析結果をHTMLに出力しています。
使用したpythonのバージョンは「3.8.0」です。
実際に作成、使用したコードは下記です。
ベタ書きでだいぶ長いです。
すみません。

import xml.etree.ElementTree as ET
from operator import itemgetter
from operator import attrgetter

class TestResultXml:
    outcome = ""
    test_name = ""
    computer_name = ""
    start_time = ""
    end_time = ""
    def __init__(self):
        self.outcome = ""
        self.test_name = ""
        self.computer_name = ""
        self.start_time = ""
        self.end_time = ""

class TestSummary:
    outcome = ""
    total = 0
    executed = 0
    passed = 0
    failed = 0
    error = 0
    timeout = 0
    aborted = 0
    inconclusive = 0
    passedButRunAborted = 0
    notRunnable = 0
    notExecuted = 0
    disconnected = 0
    warning = 0
    completed = 0
    pending = 0

    def __init__(self):
        super().__init__()
        self.outcome = ""
        self.total = 0
        self.executed = 0
        self.passed = 0
        self.failed = 0
        self.error = 0
        self.timeout = 0
        self.aborted = 0
        self.inconclusive = 0
        self.passedButRunAborted = 0
        self.notRunnable = 0
        self.notExecuted = 0
        self.disconnected = 0
        self.warning = 0
        self.completed = 0
        self.inProgress = 0
        self.pending = 0

def get_test_result(test_result_array):
    test_result_list = list()
    for test_result in test_result_array:
        test_result_item = TestResultXml()
        test_result_item.test_name = test_result.attrib["testName"]
        test_result_item.outcome = test_result.attrib["outcome"]
        test_result_item.computer_name = test_result.attrib["computerName"]
        test_result_item.start_time = test_result.attrib["startTime"]
        test_result_item.end_time = test_result.attrib["endTime"]

        test_result_list.append(test_result_item)

    test_resutl_sorted = sorted(test_result_list, key = attrgetter("test_name"))

    return test_resutl_sorted

def get_test_summary(test_result_summary):
    test_summary = TestSummary
    test_summary.outcome = test_result_summary[0].attrib["outcome"]

    counters = test_result_summary[0][0]
    test_summary.total = counters.attrib["total"]
    test_summary.executed = counters.attrib["executed"]
    test_summary.passed = counters.attrib["passed"]
    test_summary.failed = counters.attrib["failed"]
    test_summary.error = counters.attrib["error"]
    test_summary.timeout = counters.attrib["timeout"]
    test_summary.aborted = counters.attrib["aborted"]
    test_summary.inconclusive = counters.attrib["inconclusive"]
    test_summary.passedButRunAborted = counters.attrib["passedButRunAborted"]
    test_summary.notRunnable = counters.attrib["notRunnable"]
    test_summary.notExecuted = counters.attrib["notExecuted"]
    test_summary.disconnected = counters.attrib["disconnected"]
    test_summary.warning = counters.attrib["warning"]
    test_summary.completed = counters.attrib["inProgress"]
    test_summary.pending = counters.attrib["pending"]

    return test_summary

def out_test_result_html(test_summary, test_result_array):
    html_data = "<html><body>\n"
    html_data += "<title>テスト結果</title>\n"
    html_data += "<link rel=""stylesheet"" type=""text/css"" href=""stylesheet.css"">\n"
    html_data += "<html><body>\n"
    html_data += "<div class=""container""><div class=""containerleft"">\n"
    html_data += "<h1>テスト結果(概要)</h1>\n"
    html_data += "<table class=""overview"">\n"
    html_data += "<tr><th>結果</th><th>合計</th><th>実行</th><th>成功</th><th>失敗</th></tr>\n"
    html_data += "<tr>\n"
    html_data += "<td class=center>" + test_summary.outcome + "</td>\n"
    html_data += "<td class=right>" + test_summary.total + "</td>\n"
    html_data += "<td class=right>" + test_summary.executed + "</td>\n"
    html_data += "<td class=right>" + test_summary.passed + "</td>\n"
    html_data += "<td class=right>" + test_summary.failed + "</td>\n"
    html_data += "</tr>\n"
    html_data += "</table>\n"
    html_data += "<h1>テスト結果(内訳)</h1>\n"
    html_data += "<table class=""overview"">\n"
    html_data += "<tr><th>テスト名</th><th>結果</th><th>実行時刻</th>\n"
    for test_result in test_result_array:
        html_data += "<tr>\n"
        html_data += "<td class=""left"">" + test_result.test_name + "</td>\n"
        if test_result.outcome == "Passed":
            html_data += "<td class=""resutl_passed center"">OK</td>\n"
        else:
            html_data += "<td class=""resutl_failed center"">NG</td>\n"

        html_data += "<td class=""left"">" + test_result.start_time[:10] + "</td>\n"
        html_data += "</tr>\n"

    html_data += "</table></div></div></body></html>\n"

    return html_data

tree = ET.parse(".\\utest_result.xml")
root = tree.getroot()

title = root.find(".//{http://microsoft.com/schemas/VisualStudio/TeamTest/2010}Times")
results = root.findall(".//{http://microsoft.com/schemas/VisualStudio/TeamTest/2010}UnitTestResult")
result_summary = root.findall(".//{http://microsoft.com/schemas/VisualStudio/TeamTest/2010}ResultSummary")

test_result_array = get_test_result(results)
test_result_summary = get_test_summary(result_summary)
html_data = out_test_result_html(test_result_summary, test_result_array)

with open('.\\jinja2_test.html',mode='w',encoding="utf-8") as f:
    f.write(html_data)

このコードの使い方ですが、任意の場所に上記pythonコードを配置し、変換対象の結果ファイルを「utest_result.xml」という名前で保存します。
そしてpythonコードを実行します。
その結果、「index.html」というファイルが作成されます。
このファイルを表示することで、前出のような結果が表示できます。

5.まとめ

今回のエントリでは、前回のエントリで「完全に改善できていない」としていた、「テスト結果ファイルの読める化」の方法を書きました。
この方法では、しかし同時に1つの結果ファイルのみです。
また、前回のエントリで書いたbatファイルとは別に実行する必要があります。
「完全に改善できていない」と、言えると思います。

しかし効率化、自動化のための方法については明らかにできたかと思います。
次回は、これまでの方法をまとめて、テストの実行から結果の生成までを一括で自動実行する方法について書こうと考えています。

ではっ!

続き:

VisualStudioで実行した単体テストの結果の出力(3)
テスト実行からレポート生成までをツールで自動化