はじめに
私が今所属しているチームでは、バックエンドをPHPとLaravelで開発しています。
今までチームとしてユニットテストを書いていなかったのですが、これから機能開発をする際にはユニットテストを書いていこうということになりました。
ユニットテストに対する知見がチームとして薄いので、いい機会だと思い社内で勉強会を開催しました。
この記事は、その第一回目でチームに共有した内容です。今回はあくまで理論的な話で、具体的なコードはでてきません。
参考にした書籍は、Good Code, Bad Code ~持続可能な開発のためのソフトウェアエンジニア的思考です。
書籍のPart 3 ユニットテスト編の中で、優れたユニットテストの条件が5つ書かれていたので、それを私の理解を含めてまとめてみました。
破損を正確に検出する
実行結果が不安的なテストのことをフレーキーテストと呼ばれますが、フレーキーテストを作らないようにしましょうということです。
そもそもユニットテストの目的として、コードが壊れていたらそれを検知可能な状態にして、テスト対象のコードの質に自信を持てるようにするということが筆頭に挙げられると思います。
それを満たす上では、必須とも言える条件だと思います。
具体的には、以下の条件を満たすことが望まれます。
- コードが壊れていたら確実に失敗する
- コードが壊れているときだけ失敗する
実装の詳細にとらわれない
テスト対象のコードの外部から見た動作をテストすることにこだわりましょうということです。
外部というのは、テスト対象クラスを参照するクラスのことです。この条件を理解するため、まずコードに加わる変更を整理します。
まず、コードの変更は大きく二種類に分類されます。
- 機能変更
- コードの外部から見える振る舞いを変化させるもの
- 例)新機能の追加、バグ修正
- リファクタリング
- コードの内部構造的な変更
- 例)大きな関数を小さな関数に分割、処理の一部を外部に切り出して再利用性を高める
つまり、ユニットテストが外部から見た動作のみをテストするコードであれば、機能変更をした際にはユニットテストの変更が必ず必要で、リファクタリングをした際にはユニットテストの変更は必ず不要ということになります。
これだと、なぜ嬉しいのか。
それは、リファクタリングが格段にしやすくなるからです。
リファクタリングが後周りになりがちな大きな理由の一つに、既存処理壊すリスクがあるということが挙げられると思います。
しかし、この条件が守られていれば、リファクタリングをした後に既存テストが全てパスしたことさえ確認できればOKという明確な判断軸を持つことができます。
この条件を満たすには、テスト対象コードの設計段階からユニットテストのことを念頭に置いておく必要があります。いわゆる「テスタブル」というやつです。
この分野について詳しく書くと長くなるのでこの記事では割愛しますが、また別の記事で書けたらいいなと思っています。
よく説明された失敗
後からテストコードに触れるエンジニアに余分な労力を使わせないために、失敗したときになぜ失敗したのかが明確にわかるようにしましょうということです。
これを満たすために気をつけるべきなのは、一つのテストケースで一つのことをテストするということです。
いろんなテストケースを詰め込んだユニットテストは、何が原因で何のテストが失敗したのか分からなくて後から関わるエンジニアの余分な工数を取ってしまいます。
そして、テストケースの名前はできるだけ具体的にするべきです。
例えば、PHPの有名OSSであるMonologのテストコードが非常に参考になります。
いくつかテスト関数名を抜粋します。
public function testConvertRFC5424ToMonologLevelInAddRecordAndLog()
(コード)
public function testLogNotHandledIfProcessorsArePresent()
(コード)
public function testProcessorsNotCalledWhenNotHandled()
(コード)
テストケースの名前はちょっと長いですが、名前を見ただけでなんのテストをしているのかが明確にわかります。これだと、後から読んでもあまり負担ではありませんよね。
(余談ですが、やはりOSSのコードは参考になるなあとひしひしと思うエンジニア3年目です、、。1年目の頃とかOSSのコードとか怖くて読めなかった、、。)
わかりやすいテストコード
何をテストしているのかをできるだけわかりやすく書きましょうということです。
「テスト駆動開発」の翻訳者である和田卓人さんも、以下のようにポストを残しています。
この条件を守るメリットは大きく2つあります。
一つ目は、機能変更をする際にどのテストケースに影響があるのかを見つけやすくなり開発効率が上がるということです。
二つ目は、テストコードが動くドキュメントとして機能するということです。
静的なドキュメントからは読み取れない関数の詳細な動作も、テストコードを見ればわかる場合があります。
少し話は逸れるかもしれませんが、PHP Conference Japan 2022での以下の発表が面白かったです。
テストコードリーディングのみでPHPUnitの仕様を理解してみる
もちろん、普段ならドキュメントを読んだ上でもし必要ならプロダクトコードやテストコードを読むというアプローチが適切だと思いますが、たまにはこのような縛りをしても面白いですよね(笑)
簡単かつ迅速に実行できる
毎日の開発フローにユニットテストを染み込ませるためにも、実行が簡単で動作も軽いテストを書こうということです。
当たり前のように聞こえますが、私が今所属しているチームのようにこれからユニットテストを導入するチームにとっては、とても重要なことだと思います。
ユニットテスト実行に手間や時間がかかると、せっかく導入したのに工数見合いでやっぱり廃止なんていう悲しいことになってしまうかもしれません。
おわりに
ここまでお読みいただき、ありがとうございます。
今回は理論的な話で、「そりゃ頭ではわかるけど現実そうもいかないでしょ、、どうすればいいのよ!?」という問いに対しては対応策を書くに至りませんでした。
このあたりは私も勉強途中なので、また整理できたら記事にしたいと思います。