はじめに
上記の本を読んで私なりにまとめたものです。
単体テストとは
単体テストとはなんでしょうか?
ワードはよく聞きますが、その定義は曖昧だったりします。
この本では自動化されていて、次の3つの性質を全て備えるものを単体テストとしています。
- 単体と呼ばれる少量のコードを検証する
- 実行時間が短い
- 隔離された状態で実行される
逆にこれらの性質を1つでも損なっている場合は統合テストになります。
単体テストを行う目的
プロジェクトを持続可能なものにするものために行います。
単体テストを行うことで、そのプロダクトの品質を保証することができます。
小規模なプロジェクト、期間が短いプロジェクトではあまりテストの有用性が見えにくいですがプロジェクトが大規模、長期間になるほどテストが有用になります。
テストの手順
テストはArrange(準備)、Act(実行)、Assert(確認)の3つのフェーズで構成されています。
この手順で行うことで可読性が高いテストを記述することができます。
- Arrange: 変数の定義、インスタンスの生成
- Act: テスト対象の呼び出し
- Assert: 実行結果の確認
テストのアンチパターン
プロジェクトの状況にもよりますが、一般的に以下の内容はアンチパターンとされています。
- AAAパターンに沿っていない
- テストの中でifを使う
- Actフェーズが1行を超える場合は設計を見直す必要がある
- 通常1行で良いはず
- constructやsetupでインスタンスの初期化を行う
- テストを行うたびに呼び出されてしまう
- 柔軟性がない
- →テストで呼び出す専用のprivateメソッドを作成すべき
- テスト対象のメソッド名をテスト名に含まない
- 含めてしまうとテストがコードに紐づくことになる
- 本来テストがみることはアプリケーションの振る舞いなのでこれは好ましくない
- プライベートなメソッドはテストすべきでない
- 観測可能な振る舞いのみを検証するという柱に引っかかる
- プロダクションコードに用いたコードをテストコードにも記述する
- ただプロダクションコードをテストコードにコピーするのはNG
- 期待値そのものをテストコードに書く
良い単体テストの柱
良い単体テストの指標として以下の4つが挙げられます。
- 退行(regression)に対する保護
- 変更を加えた後に既存の機能が動かなくなること
- リファクタリングへの耐性
- テストを失敗することなく、リファクタリングを行えるように
- 迅速なフィードバック
- 保守のしやすさ
ワードの整理
- モック: 対象システムから依存に向かって行われる外部に向かう出力を模倣
- スタブ: 依存からテスト対象システムに向かって行われる内部に向かう入力を模倣
- ダミー: nullや一時凌ぎで使われる文字列などシンプルな値を返すために使われる
- テストスイート: 複数のテストケースをまとめたもの
- Active Recordパターン: ドメインクラスがデータベースから自身のデータを取得・保存する設計パターン
- ハッピーパス: ビジネスシナリオが正常に終わる実行経路のこと
- リポジトリ: データベースにアクセスし、そのデータを変更するクラス
単体テストの手法
単体テストには以下のような手法があります。
- 出力値ベース・テスト
- 出力される結果を検証する
- 状態ベース・テスト
- 対象の処理が終わった後に対象の状態をテストする
- コミュニケーションベース・テスト
- モックを用いてテスト対象と協力者オブジェクトとの間で行われるコミュニケーションをテストする
心に留めておきたいこと
- テストコードを含めたすべてのコードは負債であるということを理解する
- 単体テストでは通常プロセス外依存のものとの接続は行わない(データベースなど)
- 各層のテストは一つ上の層で行い、テスト対象となる層がその下にある層と何をしているかについては意識しないようにするべき
- バグが発生しうるコードにセーフティネットとなるテストを仕込んでおく
- 例)マイナスになってはいけないなど
- 単体テストはビジネスシナリオにおける異常ケースをできるだけ多く検証する
- 統合テストは一件のハッピーパスと単体テストでは検証できない全ての異常ケースを検証する
データベースに関わるテスト
基本的にデータベースはこのアプリケーションからしか編集されないので、管理下にある依存になります。
読み込みのテストは重要性が低いため、特に複雑な処理でなければテストの優先度は低くなります。
おわりに
正直まだ完全には理解できていないので、テストを記述していく中で理解を深めていきたいと思います。