新卒2年目のエンジニアが単体テストをやってみる

# はじめに
この記事は[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2)の17日目の記事となります。
昨日は @sashihara さんによる[2年間チームで毎週1on1に取り組んでわかった3つの効果 ](https://sashihara.jp/1on1) でした。

今年、ZOZOテクノロジーズでは全部で5つのAdvent Calendarが公開されています。

[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech)
[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2)
[ZOZOテクノロジーズ #3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech3)
[ZOZOテクノロジーズ #4 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech4)
[ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5)

様々な記事が投稿されていますので、皆様お時間有りましたら是非ご覧ください。

# 本記事の目的
新卒エンジニアで入社した自分はお恥ずかしいことに、今まで単体テストと呼べるものを行ったことがありませんでした。
このままではまずいと思い、単体テストなるものにチャレンジをしてみることに。
そこで、「レガシーコード改善ガイド」という本を読んで単体テスト・テストコードの重要性を理解したので、この二点に着目してみました。
それを踏まえて自分なりに今の環境でできるVBSの単体テストをやってみたので考察を載せます。

# 前置き
「レガシーコード改善ガイド」によると、レガシーコードというのは、 **テストで保護されていない扱いにくいコード** と定義されています。
**テストで保護されていない扱いにくいコード** がなぜ悪いのか？（エンジニア経験のある方ならなんとなく感覚でわかると思います）
「レガシーコードからの脱却」内の言葉を借りると以下です。

> レガシーコードとは、バグを多く含み、壊れやすく拡張が難しいコードを指します。
> このようなコードの保守と管理には多大な労力がつぎ込まれることになります。
> しかも一度作ってしまったレガシーコードの質を上げるには、初めから質の高いコードを作るよりも膨大なコストがかかります。

ざっくりと聞くだけでも課題が山積みですね。。。
「レガシーコード改善ガイド」ではそんなレガシーコードを改善する・予防するためには**テストコード**が必要と述べられています。

このことからテストコードを用いて、単体テストをやってみよう！と思い立ち、実際にやってみました。

## 環境
自分が普段扱っている言語はVBSですが、VBSには有用なテストツールがないので自前でテストコードを書いています。

# テストコードを用いた単体テスト
実践してみたテストコードの一部を抜粋。（内容は多少ぼかしてます）
これが単体テストと呼べるかも怪しいですが、、、

```vb
Call IsOverTargetAmount_test()
Sub IsOverTargetAmount_test()
	Dim amount : amount = 4999
	' Dim amount : amount = Empty
	' Dim amount : amount = ""
	' Dim amount : amount = NULL
	' Dim amount : amount = "abc"
	' Dim amount : amount = -7000
	' Dim amount : amount = 4999.9999
    response.write "amount : " & amount & "<br>"
    response.write "IsOverTargetAmount : " & (IsOverTargetAmount(amount) = False) & "<br>"
End Sub

Function IsOverTargetAmount(ByVal amount)
	IsOverTargetAmount = False

	If Not (IsNumeric(amount) And amount <> "") Then
		' 引数が数値かどうかバリデーション
		Exit Function
	End If

	If amount >= 5000 Then
		IsOverTargetAmount = True
	End If
End Function

' ▼実行結果
' amount : 4999
' IsOverTargetAmount : True
```

# 単体テストにチャレンジしてみての考察
- 可能な限り、I/Oがシンプルなものにする
    - I/Oが複雑なものはテストコードが書きづらく、レガシーコードになりやすい
    - 最低限Iは複雑でも、Oはシンプルだとテストが楽（個人的理想はBool値）
- バリデーションは以下の値に注意する
    - 変数の型（数値なのか、文字列なのか、オブジェクトなのか）
    - 数値の場合、少数や負の整数、型の限界値（INTなら -32,768 ～ 32,767）、境界値（不等号の周辺値）
    - 空文字、Empty、NULL、Nothing 等の特定のイレギュラーになりうる値
- テストコードはあくまで書いてあるコードが正しいことを確認する
    - 仕様（ビジネスロジック）が正しいかどうかはテストコードでは確認できない
- DB側は単体テストが難しい、、、
    - DBとの接続が発生する場合、モック的なテストデータが必要
    - 準備が手間
- 手戻りが手前になる
    - 結合テストフェーズになって、手戻りが発生すると厄介なのでそれを防げる
- そもそも他と依存関係のないコードでなければ単体テストとは呼べない（単体テストをするのが難しい）
- 単体テストにはホワイトボックス・ブラックボックステストがある
    - Ref : [単体テスト(ユニットテスト)とは](https://www.techmatrix.co.jp/t/quality/unittest.html)
    - ホワイトボックスではコードが正しいことを確認する
        - ホワイトボックステストのカバレッジについてはこちらを参考に
        - Ref : [ホワイトボックステストにおけるカバレッジ（C0/C1/C2/MCC）について
](https://qiita.com/bremen/items/8b6542467d2a0066e5af)
    - ブラックボックステストでは仕様が正しいことを確認する
        - 仕様の確認なので人の目によるレビュー等が必要

# 最後に
今回テストコードを書いて単体テスト（ホワイトボックステスト）を行ってみた感想としては、少なくともやる意味はあると思いました。
後戻りする機会が圧倒的に減ることメリット、主観的に完成度は上がると感じました。
しかし、プログラムの書き方や言語によってテストのハードルはかなり差が生まれるというのも実感しました。
単体テストの自動化できる言語もある中、自分でテストコードを書くのは正直工数が増える面があると思います。
また、テストのエビデンスの残し方やテストコードの管理方法等も考えなくてはなりません。
まだまだ課題は山積みですが、今後のサービスの成長に合わせて、微力ながらレガシーコード改善に取り組めればと思います。
（**ただ、レガシーコードでもサービスとしてここまで成長できたことは非常に尊敬しています。**）

---
明日は @mitanih さんが記事を書きます！ぜひご覧ください！