JUnitのテストケースにおけるパターン表の管理

:::note info
この記事は [BABY JOB Advent Calendar 2023](https://qiita.com/advent-calendar/2023/babyjob) の 6 日目の記事です。
:::


# まえおき

久しく投稿してなかったが、会社でアドベントカレンダーなる祭典を教えてもらい、乗っかってみる。

今回はJUnitにおけるパターン網羅の方法。
ではなく、パターン網羅したことをどのように残すかについて考えたので、披露したい。

今まで様々なプロジェクトに携わってきたが、こういった議論は一度もなかった。
もしかしたら、開発史上初の試みなのかもしれない。
そうなのであれば、私は喜んでそのパイオニアとなろう。
ただ、既に議論され尽くしていても、そっとしおいてほしい。
そして、パイオニア気分に浸らせたままにしてほしい。

# 問題提起

さて、このほど新しい環境で新しいプロジェクトに関わることになった。
そこでは、アジャイル開発が行われ、CICDが取り入れられて、モダンな開発が行われていた。
その中では当然単体テストツールによるテストがあるのだが、これがなかなか多い。
そして、パターン表などが残っていないため、コードから読み取る以外方法なない。
ここで考えた。
果たして、単体テストツールにおいてパターン表をどのように残すべきか？
思いつくものとしては以下のものだろう。
* Excel等でパターン表を作成し、GoogleドライブやSVNで管理する
* Excel等でパターン表を作成し、Gitで管理する
* テストクラスファイル中になんとかして残す

先２つについては、テストクラスファイルとファイルが分かれてしまうため、パターン表があることに気づかず、メンテナンスされない可能性がある。
そのため、Javadoc等にその情報の記載が必要だ。
また、テストクラスファイルとは別の場所で管理されているため、SVNやGoogleドライブ等の管理に依存してしまう。
（適切にバージョン管理されているのか、差分がきちんと管理できるのかなど）

かく言う私も今まではJavaコードをGitLab、パターン表をExcelで作成し、SVNで管理していた。
（先述した一つ目の選択肢を取っていた）
しかし、そこには以下のような課題があった。
* Javadocに`@see`や`<a href="xxxx">`でパターン表との紐付けをしていても、メンテナンスがされないことがあった
* パターン表がExcelのため、SVNでどこを修正したのかが分かりづらかった
* Microsoft Officeがメンバー分必要

そこで、私は最後の`テストクラスファイル中になんとかして残す`方法を考えた結果、JavadocにHTMLのTableタグで表を残すことにした。
こうすることで、先に私が直面した３つの課題を以下のようにクリアできると考えた。
* Javadocにパターン表があるため、別途ファイルを探さなくてよい
* Javadocへの記載のため、テキストベースで差分を比較できる
* Microsoft Officeに依存せず、どの環境でも確認ができる

# アプローチ

さて、本題である。
Javadoc上にHTMLのTableタグを残すとして、Javadoc上でのメンテナンスは少々面倒である。
そこで、会社で支給されているGoogle　Workspaceを活用し、Javadoc上にTableタグを残すためのツールを手軽に作成しようと思う。
ただし、私はGoogle App Script(GAS)はほぼ触ったことがないため、ChatGPTを活用することとする。

まずは、ChatGPTに対して、以下のようなことを問いかける。

```
GASで以下の条件を満たすコードを作成して。
・A1セルからヘッダー行、データ行のあるHTMLのTableタグを取得する
・取得したTableタグを分解してスプレッドシートに書き込む
・関数はボタンを押したら実行される
```

すると、すぐに下記のコードを作成してもらえた。

```js
function importTableFromHTML() {
  // スプレッドシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getActiveSheet();

  // A1セルの値を取得
  var cellValue = sheet.getRange("A1").getValue();

  // HTMLからTableタグを正規表現で抽出
  var tableRegex = /<table[\s\S]*?<\/table>/i;
  var tableMatch = cellValue.match(tableRegex);

  if (tableMatch) {
    // 抽出したTableタグを分解して2次元配列に変換
    var tableHtml = tableMatch[0];
    var table = parseTable(tableHtml);

    // スプレッドシートに書き込む
    writeTableToSheet(table);
  } else {
    Logger.log("Table not found in the specified cell (A1).");
  }
}

// Tableタグを分解して2次元配列に変換する関数
function parseTable(tableHtml) {
  var table = [];
  var tableRegex = /<tr[\s\S]*?<\/tr>/gi;
  var rowMatches = tableHtml.match(tableRegex);

  if (rowMatches) {
    for (var i = 0; i < rowMatches.length; i++) {
      var rowHtml = rowMatches[i];
      var cellRegex = /<t[hd][\s\S]*?<\/t[hd]>/gi;
      var cellMatches = rowHtml.match(cellRegex);
      var row = [];

      if (cellMatches) {
        for (var j = 0; j < cellMatches.length; j++) {
          var cellHtml = cellMatches[j];
          var cellText = cellHtml.replace(/<[^>]*>/g, "");
          row.push(cellText);
        }
      }

      table.push(row);
    }
  }

  return table;
}

// 2次元配列のデータをスプレッドシートに書き込む関数
function writeTableToSheet(table) {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getActiveSheet();

  // データをクリア
  sheet.clear();

  // データを書き込む
  for (var i = 0; i < table.length; i++) {
    var row = table[i];
    sheet.getRange(i + 1, 1, 1, row.length).setValues([row]);
  }

  Logger.log("Table imported successfully.");
}

```

うむ、動きそうだ。
とりあえず、Javadoc→スプレッドシートに関してはこれで良いとしよう。
では、この逆も作成してもらおう。

```
スプレッドシートの情報をhtmlのTableタグに変換してA1セルに出力するGASを作成して。
```

とお願いすると

```js
function exportTableToHTML() {
  // スプレッドシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getActiveSheet();

  // データを取得
  var data = sheet.getDataRange().getValues();

  // HTMLのTableタグに変換
  var htmlTable = convertToHTMLTable(data);

  // A1セルにHTMLを出力
  sheet.getRange("A1").setValue(htmlTable);

  Logger.log("Table exported to HTML successfully.");
}

// 2次元配列のデータをHTMLのTableタグに変換する関数
function convertToHTMLTable(data) {
  var htmlTable = "<table border='1'>";

  for (var i = 0; i < data.length; i++) {
    htmlTable += "<tr>";

    for (var j = 0; j < data[i].length; j++) {
      htmlTable += "<td>" + data[i][j] + "</td>";
    }

    htmlTable += "</tr>";
  }

  htmlTable += "</table>";

  return htmlTable;
}
```

うむ、これもいい感じだ。
あとは、スプレッドシートで1シートでI/Oさせるのか、HTMLシート/Tableシートに分けるのかで使い方を変えれば、良いかな。

# まとめ

今回は、HTML→スプレッドシート、スプレッドシート→HTMLの部分のみを作成した。
ただ、実際コードから取得するのであれば、Javadocなどのものをそのまま貼り付けたいところなので、そこはまたどこかの機会でやるとします。