テストピラミッドにそって自動テストのケースを考えてみよう

# はじめに
この記事の目的は、
- 自動テストの種類を知る
- 種類ごとにどんな責務を果たすかを知る

ことです。

# 自動テストの種類
前提として、Webシステムは3つの階層があります。
![moto.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/22c67f0b-3cb8-4354-5ee7-02a05367d2ee.png)
- UI
  - ユーザーインターフェース
  - 画面、見た目に関する部分
- サービス
  - ロジックを束ねたもの
  - 例えば、REST APIなどと捉えるとわかりやすい
- ロジック
  - データの加工など、1つ1つの処理

自動テストには、大きく以下の3種類のテストがあります。
- UIテスト
- 結合テスト
- ユニットテスト

## UIテスト
![ui_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/c388a378-2b4c-be8e-327e-f685aff48ec1.png)
名前の通りユーザーインターフェースから行うテストです。  
UI→サービス→ロジックを通してテストします。  
ユーザーが最初に触れるところから最深部まで、システム全体を通してテストするため、E2E(End to End)テストとも呼ばれます。  
例えば、ログイン画面におけるUIテストなら、
1. ログイン画面を表示する
1. メールアドレスを入力する
2. パスワードを入力する
3. ログインボタンをクリックする
4. ステータスコード200が返る
5. メニュー画面に遷移する

などがテストシナリオになります。  

UIテストのメリットは
- すべての層を通過しているため、UIテストに通ると動作の保証がしやすい
ことが挙げられます。  
一方、デメリットは
- テストの実行に時間がかかる
- テストが壊れやすい(UIの変更や環境の差異により動かないことがある)

などが挙げられます。

## 結合テスト
![ketsugo_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/d0c26fba-06b2-90ac-df7d-29c3d4991162.png)
ロジックとロジックのつながりなど、複数の層をまたいで行うテストです。
UIは通さず、サービス→ロジックを通してテストします。  
例えば、ログインにおける結合テストなら、
1. `"/login"にメールアドレス"test@example.com"パスワード"test"をリクエストする`
2. `ステータスコード200が返ってくる`

などがテストシナリオになります。  

結合テストのメリットは
- UIテストのような壊れやすさもなく、複数の層をまたいで確認ができる

ことが挙げられます。  
一方、デメリットは
- 結合されていることはわかるが、どこが壊れているのかわかりにくい

などが挙げられます。

## ユニットテスト
![unit_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/afab5e9e-8fd5-a5a0-512b-32a3109ecc90.png)

1つ1つのロジックに対するテストです。
例えば、ログインに関するユニットテストなら、
- メールアドレスとパスワードの組み合わせが存在する場合、trueを返す
- メールアドレスとパスワードの組み合わせが存在しない場合、falseを返す

などがテストシナリオになります。

ユニットテストのメリットは
- 実行速度が早い
- 壊れにくい
- 短いサイクルでテストと実装を回せる

ことが挙げられます。  
一方、デメリットは
- 部分的なロジックの正しさしか保証できず、複数のロジック(層)のつながりは担保できない

などが挙げられます。

# テストピラミッド
自動テストを作っていくにあたって大切な考え方が**テストピラミッド**です。  
![testpyramid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/cded43d9-ba19-8255-3342-4ebfd8948263.png)

全体のテスト設計として、UIテストを減らして、ユニットテストを増やしていくという方針です。
このような配分にする理由は、それぞれのテストの特徴が関係しています。  
先にあげた通り、UIテストは全ての層をまたいでテストができるため、品質の担保に向いています。  
しかし、実行が遅く壊れやすいため、継続的にメンテナンスしていくのが難しくもあります。  
できるだけUIテストの量を減らし、結合テストやユニットテストで吸収できないかを検討する必要があります。  

# テストピラミッドにそってテストケースを考えてみる
UIテストの量を減らして、ユニットテストの量を増やしていくという理論はわかりますが、どうやって実践していけば良いのだろうか？という疑問を持ったので、自分で実際に考えてみました。  
今回は、**テストケースをUI、結合、ユニットテストでそれぞれどう分割するか**のみに着目するため、使用する技術等についての言及は避けます。  

## 例とする機能の説明
### UIとざっくりやりたいこと
今回は、demo画面を作ることを考えます。  
![demo-front1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/ee53589c-249b-4f2f-55a0-c41ea2521cd4.png)

demo登録画面では、
- ユーザー名
- メールアドレス
- パスワード

を入力して登録ボタンを押せます。  
登録が完了すると、
「xxさん ようこそ！」というメッセージが表示されます。
![demo-front2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529362/7c55b6cb-a01b-8409-cf5c-33477e9c080f.png)


このような機能に対してのテストを考えていきます。

### アーキテクチャ
登場人物は3人で
- `demo-front`
- `demo-api`
- `user-api`

今回の開発対象は、
- `demo-front`
- `demo-api`

とします。  
`demo-api`は`user-api`という既存のapiに登録をリクエストするものとします。


### 仕様
#### 基本仕様
- ユーザー名、メールアドレス、パスワードを登録できる
- 正常に登録が完了すると、「ようこそ xxさん！」というメッセージが表示される
- 登録内容に不備がある場合はエラーメッセージを画面に表示する

#### バリデーション内容
- ユーザー名が10文字以下であること
- メールアドレスが正しいメールアドレスの形式であること
- メールアドレスが既に登録されていないこと
- パスワードがパスワードポリシーにそっていること
  - 8文字以上
  - 大文字小文字数字が使われていること

## テストケース例
### UIテストケース
正常登録できる1パターンとエラーメッセージが表示される4パターンのテストを行います。

```md
# demo画面の登録

## ユーザーは、登録をすることができる
* demo画面を開く
* ユーザー名に"テストユーザー"を入力する
* メールアドレスに"test@example.com"を入力する
* パスワードに"Test1234"を入力する
* 登録ボタンをクリックする
* "テストユーザー"のWelcomeメッセージが表示されていること

## ユーザーは、11文字以上のユーザー名で登録するとエラーを見ることができる
* demo画面を開く
* ユーザー名に"テストユーザー11文字"を入力する
* メールアドレスに"test@example.com"を入力する
* パスワードに"Test1234"を入力する
* 登録ボタンをクリックする
* エラーメッセージ"ユーザー名は10文字以下で入力してください"が表示されていること

## ユーザーは、不正なメールアドレスで登録するとエラーを見ることができる
* demo画面を開く
* ユーザー名に"テストユーザー"を入力する
* メールアドレスに"test"を入力する
* パスワードに"Test1234"を入力する
* エラーメッセージ"メールアドレスの形式が不正です"が表示されていること

## ユーザーは、登録しているメールアドレスで登録するとエラーを見ることができる
* demo画面を開く
* ユーザー名に"テストユーザー"を入力する
* メールアドレスに"existed@example.com"を入力する
* パスワードに"Test1234"を入力する
* エラーメッセージ"メールアドレスは既に登録されています"が表示されていること

## ユーザーは、不正なパスワードで登録するとエラーを見ることができる
* demo画面を開く
* ユーザー名に"テストユーザー"を入力する
* メールアドレスに"test@example.com"を入力する
* パスワードに"test"を入力する
* 登録ボタンをクリックする
* エラーメッセージ"パスワードは大文字小文字数字を使って8文字以上で入力してください"が表示されていること

```

### 結合テストケース
UIテストと同じく、正常登録できる1パターンとエラーメッセージが表示される4パターンのテストを行います。

```md
# demo画面の登録

## 正常に登録をすることができる
* user-apiの"/register"にボディ"user_info/can_register"でPOSTリクエスを送るとステータスコード"201"が返る
* URL"/register"にボディ"user_info/can_register"でPOSTリクエストを送る
* レスポンスステータスコードが"201"であること

## 11文字以上のユーザー名で登録するとエラー
* URL"/register"にボディ"user_info/error_user_name_length"でPOSTリクエストを送る
* レスポンスステータスコードが"400"であること
* エラーメッセージが"ユーザー名は10文字以下で入力してください"であること

## 不正なメールアドレスで登録するとエラー
* URL"/register"にボディ"user_info/error_invalid_mailaddress"でPOSTリクエストを送る
* レスポンスステータスコードが"400"であること
* エラーメッセージが"メールアドレスの形式が不正です"であること

## 登録しているメールアドレスで登録するとエラー
* URL"/register"にボディ"user_info/error_mailaddress_existed"でPOSTリクエストを送る
* レスポンスステータスコードが"400"であること
* エラーメッセージが"メールアドレスは既に登録されています"であること

## 不正なパスワードで登録するとエラー
* URL"/register"にボディ"user_info/error_invalid_password"でPOSTリクエストを送る
* レスポンスステータスコードが"400"であること
* エラーメッセージが"パスワードは大文字小文字数字を使って8文字以上で入力してください"であること

```

### ユニットテストケース
UIテスト、結合テストでは確認できていない項目について細かくテストしていく必要があります。  
`demo-api`の設計によって、テストケースは変わってきますが、ある程度必要と思われるようなものを挙げていきます。

```md
# demo画面の登録

## ユーザー名のバリデーション周り
- ユーザー名が10文字の場合、バリデーションエラーとならないこと
- ユーザー名が11文字の場合、バリデーションエラーとなること

## メールアドレスのバリデーション周り(メールアドレスのチェックロジックによって、パターンが増えると思います。メールアドレスの存在チェックでuser-apiが絡むのでもう少しパターン増えそう)
- 正しいメールアドレスの形式の場合、バリデーションでエラーとならない
- 正しくないメールアドレスの形式の場合、バリデーションでエラーとなる

## メールアドレスの存在チェック周り
- メールアドレスが存在しない場合、存在チェックでエラーとならない
- メールアドレスが存在する場合、存在チェックでエラーとなる

## パスワードのバリデーション周り
- パスワードが7文字以下の場合、エラー
- パスワードに大文字が入っていない場合、エラー
- パスワードに小文字が入っていない場合、エラー
- パスワードに数値が入っていない場合、エラー
- パスワードが8文字以上で、大文字、小文字、数値が入っている場合エラーとならない

## ユーザー登録周り(user-api)
- user-apiから201のレスポンスが返ってきた場合は、201を返す
- user-apiから201以外のレスポンスが返ってきた場合は、CannotRegisterExceptionをthrowする

```

## テストケースを挙げながら考えたこと
### 大まかな方針
- UIテストでは、画面表示内容がどうなっているかをassertする
- 結合テストでは、responseとして返ってきたステータスコードやエラーメッセージなどをassertする
- 各項目の細かいチェックなどはUIテストや結合テストでやらずに、ユニットテストにおまかせする
  - 例えば、パスワードのバリデーションの細かいところはユニットテストで全て吸収する
  - ユニットテストにおいて**パスワードポリシーに沿わないパスワードは弾ける**ことを担保できたと考えると、UIテストや結合テストでは正しいエラーメッセージが得られたことのみを確認できれば良い

### もやってること
- テスト量を`UIテスト < 結合テスト < ユニットテスト`にしたかったが、UIテストと結合テストの量が同じ
  - UIテストと結合テストではほぼ同じようなケースがある
  - UIテストの方でエラーメッセージ表示のケースを消すことも考えた
  - が、エラーとなるようなものを渡すとエラーメッセージが返ってくることは結合テストで担保できているが、それが正しい場所に表示できているかどうかはUIテストで再びassertしたほうが良いとの考えた
- 当たり前だけど、コード書いてみないとわからないところも多そう


# 参考文献
- 本
  - [初めての自動テスト ―Webシステムのための自動テスト基礎](https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AE%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88-%E2%80%95Web%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88%E5%9F%BA%E7%A4%8E-Jonathan-Rasmusson/dp/4873118166)
- 記事
  - [E2Eテストの導入から学んだこと](https://qiita.com/mt0m/items/7e18d8802843d9f60d28)
  - [【webシステムの自動テスト】UIテスト・統合テスト・ユニットテストの違いを解説！](https://rara-world.com/test-ui-unit-tougou/)