Open4

【学び】なぜテストコードを書くのか他

koukou

テストを書くメリット

  • 機能やストーリーの受け入れ基準が明確になる

✏️ 受け入れ基準が明確になるというのは良いメリット。最近は自分もそのストーリー(タスク)の受け入れ基準から作成するように心掛けていたりする。

  • 自然と疎結合なコンポーネントを書くようになる

✏️ これはまだ実感できるレベルには至っていないがそうらしい。Software Designのテスト特集で和田さんも同じようなことを言っていた。
テストが書きにくい設計は密結合になっている可能性がとても高く、実装と同時にテストを書く時点でそのテストの書きにくさ(密結合度合い)に気付いて、途中で設計を疎結合になるように修正していくことが出来るとのこと。

  • 何をするためのコードなのかをテストコードが説明してくれる

✏️ テストコード自体がドキュメントの役割と果たすのは重要な役割だと捉えられているよね

  • 網羅的なリグレッションテスト(回帰テスト)を行える & 安心してリファクタリングができる

✏️ テスト歴はまだまだ浅すぎるが、これは現時点でも凄く恩恵を感じているところ。とにかくリファクタリング時の心理的安全性がテストない時と比べても段違い。誰かが言っていた「テストのない状態でやるリファクタリングには意味がない(リファクタリング後の動作が正しいことを保証できないという意味で)」という言葉が思い出される。

  • DevOpsのライフサイクルにセキュリティを組み込み安全なデプロイを可能にする

✏️ 現在主流のアジャイル的なサイクルでリリースするためには必須の機構となっている気がする。リリースOKの指針になるのは大きい。

koukou

テストの規模

小テスト

一つのプロセス(スレッド)で完結するテストです。
全体の80% を締めます。
速度と決定性が高く、忠実性はやや低いです。簡単に作ることが可能で保守しやすいです。
ユニットテストと呼ばれるものは大体これにあたります。
コードを1行修正したらテストを実行するぐらい高頻度なため、速度が重要視されています。
決定性を高めるために 依存性の注入(Dependency Injection) などが重要になります。

✏️ 「決定性」と「忠実性」という言葉初めて知った。
決定性とは、依存性が低くパラメータが同じなら必ず結果が同じになること。
忠実性とは、どれだけ本番と同じ状態の結果を得られるかということ。
ユニットテストのような小テストでは決定性が高く、逆にE2Eのような大テストでは忠実性が高いとのこと。

中テスト

1マシン内で完結するテストです。複数プロセス(マルチスレッド処理など)やインメモリDBと連携したテストなどが対象になります。
全体の15% を締めます。
速度と決定性はやや低いです。忠実性は小テストよりは高いです。
DBに入れるテストデータや処理順番などを意識して作る必要があるため、小テストよりもめんどくさいです。そういった理由で保守もやりづらいです。
インテグレーションテストと呼ばれるものは大体これにあたります。

✏️ 小テストではDB疎通部分などはモック化して対応していたが、実際にDB接続のテストが出てくるのはこのあたりから。DB疎通周りなどの依存性のある部分が対象になってくる。LaravelではFactoryの活用や、PHPUnitのassertDatabaseHasassertSendなどが使われだすイメージ。

依存性とは、DB、メール、今日の日付、ファイル、ユーザー入力など、関数の外側の状態によって関数内の結果に影響を与える要因。結果が固定されないのでテストもやりづらい。

koukou

初めてのテストコードの書き方

テストの対象

そのシステムが扱っているアーキテクチャパターンによってテストする場所は変わります。しかし最適な場所の本質はどれも同じです。
本質は依存性を注入しやすく、フレームワークと分離しやすいコードをテストすることです。なかでもユースケースを扱っているコードが最適です。
速度と確実性が高くて保守もしやすく、忠実性もそこそこあり、テスト対象も多くない(少ないとは言っていない)のがポイント高いです。とてもコスパがいいです。

ドメイン駆動設計の場合はアプリケーションサービス層が最適です。
大抵の場合、アプリケーションサービスレイヤーはユースケースを扱っていて、フレームワークとも分離されているので作りやすいです。

✏️ 基本的にはユースケース単位でユニットテストを書いていく方針がいいとのこと。

テストの置き場所

一般的には以下の 3 つの候補があります。
私は 3 番目のやり方が好みです。

  • src ディレクトリと同じ階層に test ディレクトリを作る
  • テスト対象のファイルが有るディレクトリ内に__test__ディレクトリを作る
  • テスト対象のファイルが有るディレクトリにそのままテストファイルを入れる(.spec.ts のような拡張子になる)

理由
対象ファイルの近くにあるのでファイル間の移動が楽
どのファイルに対してテストを行っていないかがひと目で分かる
コードレビュー時に実装コードとテストコードが近くに表示されるのでレビューしやすい

✏️ 自分は元々あまり知見がなくて、1番目のやり方でやっていたが、確かに3番目の方法の恩恵は大きいなとここ最近感じているので、これからはテストファイルも近場に置く方針を採っていこうかな。

ケント・ベック提唱のテスト駆動開発フロー

  1. 対象機能のテストを変更または新規としてざっくりと書く
  2. テストを実行する(実装コードがないのでエラーになることを確認)
  3. 実装を書きながら、テストも書いてテストを動かす(完了速度重視でかなり汚く書いて良い)
  4. 実装を完了しテストをすべて成功にする
  5. リファクタリングしながらテストを動かす
  6. リファクタリングを完了しテストをすべて成功にする

✏️ これは今後倣っていきたいところ。💪