「うちのサービスはテストを書いたことがない」と聞くと、信じられないと感じる人もいる一方で、特にスタートアップのサービスなんかではよくある光景じゃないでしょうか。
私自身が勤めているバイト先も、自動テストの環境は整っているものの、テスト自体はほとんど書かれていないサービスでした(し、今もまだまだ書けてはいない)。
これは、去年の夏に行ったインターンでTDDを学び、「これは楽しい!っていうか冷静にテスト0とかヤバいでしょ」と考えた私が、まずは見よう見まねでテストを書き、次いでそれを社内に根付かせるため、ついでに自分の考え方について社員の方に感想をもらうために、社内勉強会で話した話です。
テストを書くと何が嬉しいのか?
- サービスの品質が保証される
- 当たり前だけど大事な話。本番でバグ出してhotfixしたくないでしょ!
- 当たり前だけど大事な話。本番でバグ出してhotfixしたくないでしょ!
- (多くの場合)トータルの開発コストが減る - 多くの場合は**「テストコードを書く手間 < 手動テストを繰り返す手間」** - テストを書くことで機能要件がより明確になったり、要件に対する実装の網羅性が上がったりする
- サービスについての理解が深まる - 既存のコードにテストを追加しようとするときは、コードや要件の理解が不可欠になるので、**他の人が書いた実装を理解できる**(せざるを得ない) - ~~その分闇と向き合うことにもなる~~
- コードレビューのコストが減る - テストコードがあると、何をしたいコードなのかということが**他の人からもわかりやすい** - テストケースのレビューをしてもらうことで、**機能要件段階での見落としが減る**
- 綺麗なコードが書けるようになる - テストを意識するとコードが綺麗になる(後述)
- っていうかテスト書いて開発するの**楽しい(重要)** - TDD: Test-Driven Development - Testが開発(Development)をDriveする感覚は楽しい
テストを書くと何が困るのか?
テストを書くことで起こる問題は、基本的には全て開発コストの増大に集約される。
テストコードの実装・保守・レビューなどなど、書くコードが増えるんだから単純に増える作業はある。
しかし、先に述べたように、テストを書くことで減少するコストも存在する。
なので、めっちゃ単純な話をすると**「テストによって増大するコスト > テストによって減少するコスト・リスク」になるなら書かなくてもいい**。
(あくまでも経験0からの習慣化を目指すという観点でいうなら)無理に全部テストを書く必要はないし、コストの判断とかよくわからなかったら、書いてて楽しくないなら書かなくていい。
「つらい」という感情は開発コストを正直に表現するので、手動テストを繰り返してる方がつらくないなら、その部分はテストなくてもいい。
いつテストを書くべきか?
原則としては楽しく書けるなら書く、無理そうなら相談する。とした上で、以下のケース等では意識して書くようにするとよい。
- 重要な機能周りを開発するとき
- さすがに多少無理してでも書いてほしい(さっきの話と矛盾するけど)
- まぁやらかすとシャレにならん場所は、テストなしだとそれこそ膨大な手動テストをすることになるので、概ね書いた方が楽
- リファクタリングをするとき - 既存機能を壊さないことを確認するため - すでに機能が明確になっているので、テストを書きやすい - テストを書く→通ることを確認する→リファクタするの流れで開発できるので、テストファーストで開発を進める練習におすすめ
- hotfix終了後 - さすがに「今障害起きてるからこれ直して!!!」って状態でちんたらテスト書く必要はない - とはいえ一度障害が発生した場所なので、再発防止のためにテストを足すとよい - 他のエンジニアが原因を把握するのにも役に立つ
どこまでテストを書くのかという話は、勉強不足ゆえ今回は省略。
詳しく知りたい人は、ペアワイズ法とかでググるといいかもしれない。
参考: テストの数を減らそう!プリキュアで学ぶPICT
何をどうテストすればいいのか?
テストを書こうにも、書いたことがないために「そもそも何をどうテストすればいいかわからない」場合は、まずは各機能の入口と出口を意識するとよい。
当たり前のことではあるが、全ての機能には入口(入力)と出口(それに対する出力)がある。
たとえばMVCの考え方に則れば、
こんな感じ。
この上で、各ユニットに対して**「この入力のときにこの出力が得られる」**といったことをテストするのがUnit Testであり、テストの基本。
例なので細かい点は違和感があるかもしれないけど、この図で言えば3の入力に対して4の出力が得られることをテストするとModelのテストになるし、2の入力に対して6の出力が得られることをテストするとControllerのテストになる。
逆に言うと、2の入力に対して5の出力が得られることをテストしようとかしたときは、入力と出力の対応がうまくいっていないのでよくわからないことになる。
全ての矢印に対して妥当性を確認していくのがIntegration Testになるけど、これはコストが大きくなるので、最初はあまり書かなくてよいと思う。Unit Testが完璧に書けていれば、理屈の上ではインタフェース部分のテストのみになるので、重要度が低くなるということもある(※ここの認識は何かがズレている気がするけど)。
テストを意識したコーディング
「テストは機能の入口と出口に対して書く」ということは、テストを実装しやすくするためには、入口と出口の対応が綺麗なコードを書くべき。
このことを意識すると、なるべく各ユニットの入力と出力を一対一にしていこうという話になるため、たとえば
こういうFatty Controllerのような実装が減る(こういうロジックを1枚目の図に示すようなロジックに変えたくなる)。
綺麗なコードはテストがしやすいので、テストを意識するとコードが綺麗になる。
具体的なコード(Rails + minitest)
テストコードは**「何かやって(インスタンス生成したりリクエスト投げたり)、結果を確認(assert)する」**の繰り返しが基本。
test "レコードがないときに#editしようとすると#newに飛ぶ" do
# 何かやって
get :edit, id: 1
# 結果を確認(assert)する
assert_redirected_to controller: 'articles', action: 'new'
end
構文とかの話はRails テスティングガイドを読んでください。
まとめ・雑感
とにかくテストを書こう。わからなかったり困ったら書ける人に聞こう。
と、ここまでが勉強会で話した話を短くまとめたものです。
ここからはこの記事を書くにあたって他の記事を読んだりした上で思ったことになります。
今回は、テストを書いたことがないメンバーに対して「とにかくテストを書いてみよう!」という話をすることが目的だったので、「カバレッジを上げろ」だとか「こういうケースではマストで書け」とかいう話は全然していません。見ようによってはふわっとしてるし、生ぬるい話になっているかと思います。
しかし、気持ちとしては、まずは「テストって楽しい!」と思ってもらえないと絶対に手が動かんだろうなというのがありました。ちなみにこの辺りの話はテストコードは「書けるようになる」ものじゃなく「書きたい」と思うもの(ポエム)という記事が言いたいことを言ってくれていました。
後は、TDDを行った時にぶつかった7つの壁のコメント欄で、「TDDのためのテストと、品質保証のためのテストは違う」といった旨のことが言われていて、これがすごく腑に落ちました。
要するに、TDDはテストを用いた開発手法であって、その付随効果として、一定の品質が保証されるということです。ガチガチに品質保証をしようとするなら、それとは別に工数を割いてがっつりテストを書く必要があります。しかし、今までテスト経験がないというところから始めるのであれば、TDDによって付随的に得られるレベルの品質保証でも、十分に意味があると思います(実際自分の書くコードのバグは、テストを書き始めてから格段に減りました)。
したがって、この記事のまとめとしては、**「最初は負担にならない量でいいからテストを書いてみよう。テストを書けば開発も速くなるし副作用的にバグも減るよ!それでもテスト0と比べれば十分な品質保証が見込めるよ!」**という感じになるかと思います。