レガシープロダクトにおける自動テストの優先度を考察する

## はじめに
### 概要
自動テストのないレガシープロダクトを改善するために
自動テストの手法を以下の観点から比較し、優先順位を決めました。
1. 信頼性
1. 安定性
1. 網羅性
1. 実行時間
1. 実装時間


### なぜこの記事を書くのか
半年ほど前に長く運用されているプロダクトを引き継いだのですが、
決まったテスト手順もなく、内部ロジックの知識もなく、何をテストすればいいのかわかりませんでした。
最近になって内部ロジックまでキャッチアップできてきたので、テストを自動化したいと思うようになりました。
せっかくなので比較ついでにこの記事を書くことにします。
~~ちなみに本当に実施するかはまだ決めてません~~

この記事が、**これから自動テストを始めたいけど、何から始めるかわからない**
といった悩みを解決する手助けができたら幸いです。

### 対象読者
* テスト自動化をこれから進めたい人
* テストが全く行われてない環境にいる人
* テスト項目が毎回定まっていない環境にいる人
* ~~テストが毎回ノリの環境にいる人~~

## なぜテストを自動化する必要があるのか
なぜテストを自動化する必要があるのでしょうか？

それはプロダクトが拡大するにつれて、既存機能への影響をテストするための工数が膨れ上がるからです。
開発初期の頃は最低限の機能しか作らないため手動テストで問題ないかも知れません。
しかし開発が進むにつれ、機能が追加され、やがて既存機能に必要な前提条件やテスト方法を忘れます。
実装を読めば済む話と思わないでください、その分工数がかかり、しまいには日が暮れます。

またプロダクトは常に同じ人に運用されるとは限りません。
web系の企業の場合人の移り変わりが激しいので、次第にテストの勘所のような暗黙知は失われてしまいます。

加えて既存の機能も含めてテストを漏れなく行おうとすると、
工数がどんどん増えて、リリースまでの時間が伸び、開発者のやる気が削がれてしまいます。

自動化さえしてしまえば、勘所は機能がある限り残り続けますし、
既存の機能のデグレ確認のためのテストに多くの時間を費やす必要もないでしょう。
逆を言えば、追加の機能開発の予定もなく、
納品してしまいさえすれば終わり or 運用だけのプロダクトには自動テストは不要でしょう。


## 自動テストの種類
### 1. 単体テスト（ユニットテスト）
ここではユニットテストをコードベースで記述された、関数やクラスといった個々のコンポーネントが
正しく動作していること（機能要件[^1]をみたいしていること）を確認するテストを指すことにします。
ローカル環境など実際の運用とは異なる環境で動作させることが多いです。

- **メリット**
    - 導入が簡単
    - 実装に近いテストのため、コードの局所的部分をテストすることも可能
- **デメリット**
    - インタフェースエラーが検知できない
    - 実際の環境で起こる不具合の検知ができない
        - 環境変数のエラー
        - DB接続時のエラーなど

### 2. 結合テスト
ここでは単体テストで検証したコンポーネントを組み合わせた際に、
機能要件通りに動作することを確認するテストを指すことにします。
こちらもローカル環境で実行されることが多いです。
コードベースで実行さすることも、手動で実行することもできますが後者が多い印象です。
こちらはコードベースで実行される場合、環境はDBUnitなど擬似的なものが使用されます。
ただし結合テスト用のDocker環境を用いて自動化することで本番と近い環境で実行することができます。

- **メリット**
    - インタフェースエラーなど単体テストより広い範囲を検査できる。
- **デメリット**
    - 導入が少し大変
    - 実際の環境で起こる不具合の検知が難しい（ある程度検査可能）
        - 環境変数のエラー
        - DB接続時のエラーなど

https://kazuhira-r.hatenablog.com/entry/20170310/1489161965

### 

### ３. 総合テスト(機能テスト・E2Eテスト)
システムテストとも呼ばれるテストです。
ここでは実際の運用に近い環境（ステージング環境）などにデプロイして行うテストのことを指すことにします。
機能テスト、操作テスト、負荷テストなどさまざまな種類がありますが、
完全自動化することができるのは実際の運用手順に則って検証される
機能テスト（E2Eテスト）だけです。よって機能テストに焦点を当てて比較することにします。

- **メリット**
    - 実際の環境で起こる不具合の検知が難しい（ある程度検査可能）
        - 環境変数のエラー
        - DB接続時のエラーなど
- **デメリット**
    - 自動化するのがものすごく大変
        - Seleniumによるブラウザの自動操作などが必要
    - 自動化できない場合もある
    - 環境のデータに依存するため不安定になる場合もある


## テスト方法比較
自動テストにも手動テスト同様に単体テスト、結合テストなどさまざまなアプローチがあります。
まず最初に各テスト方法の概要とメリットデメリットを調査していきます。
上記を踏まえた上で以下の五つの観点から比較してゆきます。
1. 信頼性
1. 安定性
1. 網羅性
1. 実行時間
1. 実装時間

:::note info
以降比較表で用いる ● ▲ ✖️はあくまでスコアを三段階で表したものです。
:::

### 1. 信頼性
|単体テスト|結合テスト|機能テスト|
|:------------:|:------------:|:------------:|
|　✖️　|　✖️ 〜 ●　|　●　|　


信頼性の観点では機能テストに軍配があがる。本番環境に近いサーバーにデプロイされ、
DBやキャッシュも本番に近いものに接続される環境でテストされるため信頼性が高いテストを行うことができる。

一方結合テストやユニットテストでは環境が疑似的なものになるため高い信頼性を約束することができません。
しかしDocker環境を用いた結合試験では信頼性を機能テストと同等のもにすることができます。


### 2. 安定性
|単体テスト|結合テスト|機能テスト|
|:------------:|:------------:|:------------:|
|　●　|　●　|　✖️　|　

機能テストはステージング環境にデプロイされるケースが多いですが
既存のテストデータが既に環境に溜まっているてテストデータに影響を及ぼす可能性もあります。
またテストのためにデータをコントロールするようなバッチをキックする必要があったりなど
環境特有の操作が必要になるケースもあり、安定性には疑問が残ります。

一方単体テストや結合テストはボータビリティーを意識して実装されることが原則となっているので
他のデータやシステムの影響を受けることがなく安定性が高いと言えます。

### 3. 網羅性
|単体テスト|結合テスト|機能テスト|
|:------------:|:------------:|:------------:|
|　●　|　✖️　|　✖️　|　

機能テストはUIからの操作が前提のためシステムの細部までテストすることが困難です。
結合テストもAPIなどの機能のインターフェース部分から実行されるため同様に困難であると言えます。
一方単体テストはTDDの普及に伴いエンジニアに浸透しているのと
ホワイトボックステストのような細部を狙ったテストが作りやすいので網羅する範囲が広いと言えます。


### 4. 実行時間
|単体テスト|結合テスト|機能テスト|
|:------------:|:------------:|:------------:|
|　●　|　▲　|　✖️　|　

こちらはもはや説明不要だと思いますが
網羅する範囲が広くなれば広くなるほど実行時間がかかります。
また機能テストは並列実行できないため、完了までより時間がかかります。

### 5. 実装時間
|単体テスト|結合テスト|機能テスト|
|:------------:|:------------:|:------------:|
|　●　|　✖️ 〜 ▲　　|　✖️　|　

機能テストの実装にはSeleniumなど専用のライブラリやツールの学習コストがかかります。
結合テストの自動化にもDB接続部分など普段使いしないライブラリの学習が必要になる場合もあります。
一方ユニットテストはそれらが必要ないので実装にかかる時間は短くて済みます。


## まとめ ＆ 考察
上記で比較した表をまとめて、合計スコアを出してみました。
スコア内訳（✖️: 1点, ▲: 2点, ●: 3点）

|評価項目|単体テスト|結合テスト|機能テスト|
|:------------:|:------------:|:------------:|:------------:|
|信頼性|　✖️　|　✖️ 〜 ●　|　●　|　
|安定性|　●　|　●　|　✖️　|　
|網羅性|　●　|　▲　|　✖️　|　
|実行時間|　●　|　▲　|　✖️　|　
|実装時間|　●　|　✖️ 〜 ▲　|　✖️　|　
|合計スコア|　13　|　9 〜 12　|　7　|

こう見ると単体テストが一番費用対効果が高いように見えます。

しかしながらビジネス的にクリティカルな機能に関しては**信頼性**の重要度が非常に高くなるため、
ビジネス的にクリティカルな機能に関する機能テストor結合テストを実装し、
その後単体テストの網羅率を上げるのが一番費用対効果が高そうに思いました。


## 参考文献
###  参考記事
https://speakerdeck.com/autifyhq/debusami2021-sohutoueatesutozi-dong-hua-ziyani?slide=23

https://www.praha-inc.com/lab/posts/e2e-testing

https://qiita.com/gevanni/items/ff9a27936a1a6df28b9a#1-%E6%80%A7%E8%83%BD%E3%83%86%E3%82%B9%E3%83%88 

https://adoc-teslab.com/blog/dev-qa-test-automation/4813/

### 各テスト定義参照元
https://www.sophia-it.com/content/%E5%8D%98%E4%BD%93%E3%83%86%E3%82%B9%E3%83%88

https://www.sophia-it.com/content/%E7%B5%B1%E5%90%88%E3%83%86%E3%82%B9%E3%83%88　

https://www.sophia-it.com/content/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%83%86%E3%82%B9%E3%83%88

[^1]: 求められる機能の振る舞いが定義されているもの