t0mmy

2020年12月30日に参加

学習履歴詳細

単体テストの考え方・使い方 6章 単体テストの3つの手法

やったこと

  • 単体テストの考え方・使い方 6章 単体テストの3つの手法 読了

学んだこと

学び

  • 単体テスト 3つの手法(というより、性質に沿った分類)
  • 参照透過性という考え方
  • カプセル化と関数の不変性は、どちらも不変条件の維持が目的

オブジェ九思考プログラミングは可変の部分をカプセル化することでコードを理解しやすくする。一方、関数型プログラミングは可変の部分を最小限にすることでコードを理解しやすくする。

  • 関数型アーキテクチャとヘキサゴナルアーキテクチャは、副作用の扱いが異なる
    • 関数型アーキテクチャは、「より厳しい制約を課したヘキサゴナルアーキテクチャ」と見ることが出来る
  • 関数型アーキテクチャは、プロセス外依存の呼び出しが増えるため、アプリケーションコードのパフォーマンスが低下しやすい

気付き

  • 純粋関数と副作用を、極限まで分離すると、関数型アーキテクチャになるイメージ
  • 「関数的核」、「可変核」と、「指揮するアプリケーション・サービス」に分離する、というアプローチなら、オブジェクト指向言語でも十分実装できそう

感想

  • 「可変核にて、関数適格に渡す全ての入力値が集められる」
    • 入力値を集めるだけの関数が爆誕するイメージ。コードはとても読みにくそう
  • この章でいう保守容易性は、「いかに壊れにくいテストを書くことが出来るか」に焦点が当たっているように読める
    • 保守容易性は、テストの存在だけではない
    • テストに全振りした結果、読みにくく、理解に時間のかかるコードが生まれては、保守が困難になりそう
    • ここはトレードオフだと思う
    • 極限まで関数型アーキテクチャを突き詰めると、テストは壊れにくくなるが、可読性(他にもあるかも)が損なわれる気がする

メモ

単体テスト3つの手法

単体テストには、以下三つの手法がある。

  • 出力値ベース・テスト(戻り値を確認するテスト)
  • 状態ベース・テスト(状態を確認するテスト)
  • コミュニケーション・ベース・テスト(オブジェクト間のやり取りを確認するテスト)

上記手法は、一つのテストケースに一つだけ適用する事も、複数適用することも可能。

■出力値ベース・テスト

テスト対象に入力を与え、得られる出力を検証するテスト。
冪等性が担保されること(≒テスト対象の関数・メソッドに、副作用がない事)が前提。

■状態ベース・テスト

検証したい処理を実行した後、テスト対象の状態(テスト対象はもちろん、強力オブジェクトやDB等のプロセスが外依存)を検証する。

■コミュニケーション・ベース・テスト

モックを用いてテスト対象システムとその協力者オブジェクトとの間の通信を検証する。

単体テストの3つの手法の比較

単体テスト3つの手法を、単体テスト4本の柱の観点から評価する。

出力値ベーステスト 状態ベーステスト コミュニケーションベーステスト
リファクタリング耐性の維持に必要なコスト 低い 普通 普通
保守容易性の維持に必要なコスト 低い 普通 高い

以上より、可能な限り、出力値ベーステストで実装すること。

以下補足

  • 退行保護は、単体テストの手法に影響を受けない
    • 退行保護に影響を与えるのは「テスト時に実行されるプロダクションコードの量」「コードの複雑さ」「ドメインの重要性」の3つ
  • 迅速なフィードバックは、単体テストの手法に影響を受けない
    • プロセス外依存を使わなければ、速度にそれほど差はでない
  • リファクタリング耐性と、保守容易性は、単体テスト手法によって差が出る
    • 保守容易性は、以下によって把握できる
    • テストケースの理解がどの程度困難か : 一般に、コード量やコードの可読性の影響を受ける
    • テストケースの実行がどの程度困難か : プロセス外依存の数の影響を受ける
    • 「状態ベーステスト」は、保守性がやや低い
    • ヘルパーメソッドの導入や、クラスの値オブジェクト化によって、ある程度軽減可能

関数型アーキテクチャについて

最も優位な「出力値ベーステスト」は、テスト対象が純粋関数であることが求められる。
テスト対象に隠れた入力や出力が存在すると、テストが困難になる。
隠れた出力には「副作用」「例外」が、隠れた入力には「内部または外部の状態の参照」がある。

参照透過性を持つかどうかで、テスト対象が純粋関数かどうかを判断できる

  • 参照透過性 : 振る舞いを変えることなくメソッドを呼び出している部分を値に置き換えることが出来る能力
    • ロジック部分を無視して、メソッドに期待されている出力(リテラルなど)に置き換えた状態で、呼び出し元が正常に動作するか

関数型アーキテクチャでは、コードを以下の二種類に分離することで、ビジネスロジックと副作用の分離を達成する。

  • 関数的核(functional core)または不変核: (副作用を持たない)決定を下すコード
  • 可変核(mutable shell) : 決定に基づくアクションを実行するコード

入力値の準備や副作用を持つ処理は、可能な限り可変核で行う。

  • これにより、単体テストでは、出力値ベーステストを用いた関数的核だけ検証すればよい
  • 可変核のテストは、統合テストで実施する

カプセル化と関数の不変性は、どちらも不変条件の維持が目的。

  • カプセル化は、不変条件に絡むパラメータの変更に、特定の手続きの経由を強制することで、不変条件を維持する
  • 関数の不変性は、副作用を持たないようにすることで、不変条件を維持する

オブジェ九思考プログラミングは可変の部分をカプセル化することでコードを理解しやすくする。一方、関数型プログラミングは可変の部分を最小限にすることでコードを理解しやすくする。

関数型アーキテクチャとヘキサゴナルアーキテクチャの違いは、副作用の扱い方。

  • 関数型アーキテクチャでは、副作用を、全て関数的核の外に出す
  • ヘキサゴナルアーキテクチャでは、ドメイン層で完結するのであれば、副作用は許容する
    • 言い換えると、あるドメインの副作用はドメイン内で収まる必要があり、ドメインの境界を超えてはならない

関数型アーキテクチャの欠点

■ どのようなアプリケーションにも、関数型アーキテクチャのアプローチを導入できるわけではない

入力値を事前に集める事が出来ない場合など。

例) あるビジネスロジックの結果を基に、プロセス外依存から値を取得する場合

■ アプリケーションコードのパフォーマンスが劣化しやすい

  • プロセス外依存を呼び出す処理が増えてしまうため

■ コードベースが肥大化する

大体、関数的核と可変核へ分離するコードと、指揮を執るコードが要因。

複雑なシステムを構築する際は、コードの複雑さが減少し、保守容易性のメリットを享受できる。

しかし、そこまで複雑ではないシステムを構築する場合、保守容易性のメリットを享受できず、肥大化したコードだけが残る結果となる。

最後に

全てのテストケースを出力値ベーステストにする必要はない(というかできない)。

出来るだけ多く出力値ベーステストにすること。

言い換えると、関数の純粋性を厳格に維持する必要はない。


この章で扱う事

  • 単体テストの手法の比較
  • 関数型アーキテクチャとヘキサゴナルアーキテクチャとの関係
  • 出力値ベース・テストへの移行

まとめ

  • 出力値ベース・テストとは、テスト対象システムに入力値を与え、得られた出力を検証する単体テストのこと
    • 冪等性のある関数やメソッドなど
    • リファクタリングで壊れにくく、保守性が高いテストとなる傾向がある
  • 状態ベース・テストとは、テスト対象システムと協力者オブジェクトの状態を検証する単体テストのこと
    • 出力値ベース・テストと比較して、テストが壊れやすいため、注意が必要
    • テストのために、 private メンバを公開してはならない
    • ヘルパーメソッドや値オブジェクトの活用により、ある程度軽減可能
  • コミュニケーション・ベース・テストとは、、モックを使ってテスト対象システムと協力者オブジェクトのコミュニケーションを検証する単体テストのこと
    • 出力値ベース・テストと比較して、テストが壊れやすいため、注意が必要
    • 以下の理由により、上記テストと比較して最も保守が難しい
    • モックを使用することによる、実装への依存度が上がり、リファクタリングで壊れやすくなる
    • モックを使用するための準備コードが必要であり、コード量が増えがち
    • そのため、コミュニケーションベース・テストは、以下を両方満たす場合にのみ実装するよう、制限する
    • 検証対象の通信がアプリケーションの境界を超えて行われること
    • 外部から確認できる副作用を含むこと
  • 関数型プログラミングを導入するモチベーションは、ビジネスロジックと副作用の分離である
  • 関数型アーキテクチャでは、全てのコードを以下の二つに分類する
    • 関数的核(functional core) : 決定を下すもの
    • 可変殻(mutable shell) : 関数的核に入力値を提供したり、関数的核で下された決定に基づいて副作用を含む処理を実行する
  • 関数型アーキテクチャとヘキサゴナルアーキテクチャの違いは、副作用の扱い
    • 関数型アーキテクチャ : 全ての副作用をドメイン層の外に出す
    • ヘキサゴナルアーキテクチャ : ドメイン層で完結するような副作用は許容する
    • => 関数型アーキテクチャは、「副作用についてきつい制約を課したヘキサゴナルアーキテクチャ」と見ることが出来る
  • 関数型アーキテクチャとヘキサゴナルアーキテクチャは、保守とパフォーマンスのトレードオフ
    • 関数型アーキテクチャは、保守容易となる代わりにパフォーマンスが犠牲となる
  • 関数型アーキテクチャの採用判断は慎重に
    • あまり複雑ではないシステムだと、関数型アーキテクチャを導入する初期コストが無視できない事になる
テスト

2023年04月17日(月)

2.0時間