モダンな環境でテスト書いてみて良かった！から始まる話

この記事はフリューAdvent Calendar 2022の1日目の記事となります。

最近行った開発を通じて感じたテストに関する疑問から、色々調べたり感じたことをまとめていきます。
技術的知見はあまりありません。どちらかというとポエムです。

## この記事で話していくこと

* 自動テストは良い
* テストについて考察
    *  どれくらい書きますか？
    *  レガシーにどう対応しますか？
    *  どうやって保守しますか？
  

## きっかけ ： 最近の出来事

最近 Kotlin + Spring WebFlux でマイクロサービス的なサーバコンポーネントを作成したりしました。(ちゃんと開発したのは、およそ数年ぶりです。)
開発体験はかなり良くて、個人的には特にテストがサクサクと書けるところが気持ち良かったです。

Java/JVM系において馴染みのある、Mock Objectを利用したクラスごとのユニットテストから、TestContainers(https://www.testcontainers.org/) を用いたデータベースを立ち上げる結合テストまで、様々な体験が良かったです。Springにロックインされると、さらに便利に色々出来て良さそうです。

テストの実行はGithubActionsで行い、pushと同時にテストが走り、結合テストを含めてもフィードバックサイクルが短くリズムが良かったです。

良きを何回言ってるんだ...


### 開発をする中で、テストに興味を持つまで

さて、今回のサーバコンポーネントの開発の進め方ですが、以下のように進めて行きました。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20943/49e0c7b8-3e17-cd60-44e0-e212c05134cf.png">

#### STEP1. 雑に動かし、「動作する」を目指す

要求仕様が探索的で、設計もかなり選択肢が多かったため、まずは雑に動くものを作って整合をしていきました。
まずはローカルPC環境で、REST APIのインターフェースやデータベースの設計については最小限に、まずは動かしてみました。


#### STEP2. とりあえず動作する状態から、きれいで保守出来る完成形を目指す

動作した後色々と試してみて、ファーストリリースにおける仕様を整合し、REST APIとデータベースの設計を行いました。
設計が固まったところでの進め方としては、

A) ０から作り直す
B) 動作しているものを徐々にきれいにする

が選択肢としてありましたが、今回は後者(B)で実施してます。図で言う上に伸びる点線ですね。
それを行うために、テストを書いて、リファクタリングを行っていくことになります。

#### STEP3. 変更があった場合でも細かく修正を加えて、動作する→きれいに動作するを積み重ねる

あとは上記STEPを繰り返す感じです。
適切なテストとIDE(今回はIntelliJを使用しました)のパワーで、サクサクと開発できていきます。

----

そういった感じで、TDDっぽい進め方を身近に感じ、かなりテストを書くことの重要性を実感しました。
一応使った技術は、以下のようなものになります。

・JUnit
・Mockito
・TestContainers
・WebTestClient(Spring Test)

最近のコンテナを用いたテスト手法をちゃんと使ってみて、古きJava由来のテスト手法から見てみると、隔世の感を感じております。
興味がふつふつと湧いたこのタイミングで、現代的なテストについて、参考文献をもとに色々考察してみます。

### そもそもテストで担保すべきことは何なのか？

テストのそもそもの目的としては「欠陥を取り除き、ソフトウェアの品質を高める」ということが挙げられると思います。
また、欠陥が見つかる工程が後になればなるほど、コストがかるという話があります(参考文献1、2)。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20943/5051e64d-fd38-39a3-cee9-28e7d7dfee91.png">

コストの比率イメージ図

図を見て分かるように、欠陥に関するコストを抑えるために、なるべく左側の工程でテストを行い品質を担保するのが効率的ということがよく言われています。Shift Leftと呼ばれるものですね。
アジャイルの文脈では、着想からインテグレーションや本番環境リリースまでが、短いスパンでインクリメンタルに行われていきます。
ガチガチに決めすぎずに、実装と検証を繰り返し学習を行っていくために、再帰的なテストと低コストでの実現が必要になります。

##### システム要件まで含めた再帰的な自動テスト
アジャイル開発であっても、セキュリティの課題などは後工程まで見落とされがちです。様々な面でShift Leftを意識して、なるべく早期に解決したり、解決できる仕組みを入れていくことが、欠陥をなくして品質を高めるのに重要ということになりそうです。ただ、最初の環境整備などに時間と技術力が必要にはなりそうですね。

### 実際にどのようなテストをどのくらい書けばよいのでしょうか？

最近のテスト環境では、自動テストに関するプラクティスも揃ってきておりツールも充実しているのですが、ともすれば無限にテストに時間を費やすことが可能です。リリースサイクルを早くするためにはテストを書きすぎても良くない状況になってしまいます。

テストに対するコストや比率はどれくらいが妥当でしょう？

参考文献1、2によると、まず３種類のテストを分けて考えていくのが妥当なようです。

| テスト手法 | Googleでの定義 | 主に使うミドルウェアなど | ゴールの目安 |　比率 |
|:-:|:-:|:-:|:-:|:-:|
| ユニットテスト | Small Test | xUnit, Mockライブラリ など | カバレッジ | 80% |
| 結合テスト | Medium Test | TestContainer(DBTest含む), WebTestClient など | NaN | 15% |
| システムテスト | Large Test | Selenium, Appium, JMeter/Gatling, Chaos Monkey など | NaN | 5% |

Googleでの定義は1プロセス(Small Test）, 1ホスト(Medium Test), 複数ホスト(Large Test)といった感じですが、無理やり馴染みやすいテスト手法とマッピングしてみました。比率はGoogleが目指している比率を参考に載せていますが、個人的にしっくりきています。

基本的にはユニットテストを充実させて、規定したカバレッジをクリアして品質担保を行っていくのが適切というところですね。
ユニットテストのコストに比例して、結合テストやシステムテストがどれくらいであるべきかが決まってくるようです。

E2EテストやUI自動テストに工数をかけすぎるのはアンチパターンであり（特にキャプチャーリプレイはかなり非難されています）、上記比率を目安にテストの改善を推進するのがコスト面でも品質面でも妥当というところのようです。なんなら手動の方が良い局面もあるかもです。
UIまで絡めたE2Eをどうやってうまくやるのかは、ちょっと調べてみたのですが、12月中にはまとまりそうもありませんでしたので、今回は保留にします。

### レガシーな既存のコードに対して自動化のアプローチはどうやるのか？

新規で各種自動テストを作っていくのは、比較的容易に出来る感じがしています。ツールも揃っています。
では、レガシーコードについてはどういうアプローチが良いのでしょうか？
参考文献1では、まずは直近の変更が多いプログラム(=HotSpot値が高い)を特定し、その上位２０％にテストプログラムを書いていきましょうという感じでした。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20943/4faa4a44-4068-02b9-73e0-ac7bf5c2d736.png">


品質に不安があって、早急に手を打たないとダメな巨大なプログラムに対しては有効な手法に見えます。
**変更点が多い箇所を特定する** というのはgitリポジトリから、gilotを利用すると実現出来そうなので、機会があれば実施してみたいなと思いました。

https://github.com/hirokidaichi/gilot

経験則から、変更箇所が多い場所からバグが出るってのは一理あるなと思います。
というか何も変更しなければ新たなバグも出ませんので、まずは変更が多い場所の品質を上げるところから、というところでしょうか。


### 自動テストがうまくいかなくなる原因はなにか？

個人的には結局のところ、定期的な見直しが出来ていないのと、規模感の問題ではないかと思っています。

極論すると、1人での開発時にはテスト書いてリリースして... というのは実施しやすいけど、1000人規模ではどうやって実現するのか？ みたいな話です。
テスト種別的観点を考えてみると、スケールするのが難しそうなポイントは、結合テストとシステムテストの扱いにありそうです。

時間経過とともにコードベースも大きくなり、モノリスでは対応しきれず、色々なサービスが増えていくということもあるでしょう。また、クラウドでは比較的扱いやすいマネージドなサービス郡の導入にも複雑さを増す原因があるかと思います。

<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20943/ff40142e-bb98-24d2-7997-81d181984383.png">

システム分割や、マネージドサービスの導入において、各テスト内容に対するコストを意識し、品質保持とコストやリリース速度をうまくバランスを取って行く必要がありそうです。気を抜くとアイスクリームコーン型テスト比率になる危険性が高いと感じます(参考文献3)。

##### テスタビリティの高いアーキテクチャ

- 変更頻度が多い箇所にバグ集約するという点
- 機能別・ドメイン別の分解とは違った観点で、変更頻度に着目した分解を行うアーキテクチャ設計(参考文献4)

上記のような、「変更頻度」に着目することが最近見聞きされるようになってきました。
テスタビリティを絡めてみても、変更頻度に着目してアーキテクチャを考察するのは結構アリではないかと考えています。適切な分解点や集約度を持ったアーキテクチャの参考にしてみても良いかもしれません。
逆コンウェイの法則を求めて、変更頻度・テスタビリティを元に組織をデザインする可能性も探ってみてたいです。開発者体験やリリース速度を重視する事業体であれば案外マッチしそうです。

ここまで考察すると、規模が違いすぎて参考にならないかと思っていたGoogleのソフトウェア・エンジニアリングから学ぶことは、多分にありそうだと最近思っています。（Googleに対する個人的印象は、超巨大なソフトウェアを何とかするために全てを最適化しているという印象です。）

**いやぁ、テストっておもしろいですね！**

### まとめ

* 自動テストは品質向上にも良いし、開発体験も良くなるはず
* 良い体験のまま運用を続けるには、色々考えてメンテしていく必要がある
* メンテしていくには、アーキテクチャや組織体制を考えてみるところまで踏み込むことになるかも
  * 事業が大きくなっていけばという前提ではある
* UIまで網羅したE2Eテストは語るのさえ難しい


### 参考文献

**1. ソフトウェア品質を高める開発者テスト**

https://www.amazon.co.jp/dp/B09YTSXFW8

**2. Googleのソフトウェア・エンジニアリング**

https://www.amazon.co.jp/dp/4873119650/

**3. Quality@Speedを実現するための5ステップ**

https://parasoft.techmatrix.jp/the-five-steps-needed-to-enable-qualityspeed

**4. ライティングソフトウェア**

https://www.amazon.co.jp/dp/B08WWH3LK3/