オブジェクト指向プログラミングでは、関数型プログラミングと比較して単体テストが難しいのはなぜですか?


9

このシリーズを進めています。作者は、オブジェクト指向プログラミングでは状態が維持されるため、単体テストを書くのは難しいと述べています。彼はまた、関数型プログラミングは(常にではない)状態を維持しないので、単体テストを書く方が簡単だとも言っています。この問題を示す例は見当たりませんでした。これに該当する場合、オブジェクト指向プログラミングと関数型プログラミングの単体テストを比較する例を教えていただけますか?


2
関数型プログラミングの方がテストしやすいという考えには賛成できません。実際のステートレス関数はステートフルオブジェクトよりもテストが簡単ですが、ステートフルオブジェクトをステートレス関数に変換すると、モナドのような複雑なステートエミュレーションを使用して、はるかに複雑なステートレス関数が生成されます。
陶酔感2017年

1
@Euphoric問題の解決策の機能的な表現は、単に状態をStateモナド(状態をエミュレートする)で置き換えることとはかなり異なる可能性があります。あなたは部分的にポイントを持っています-そのスタイルで書かれたコードは非ローカルな相互作用を持っています-しかしユニットテストの目的のためにあなたはまだ明示的に状態を渡すことができるだけで、あなたは何もモックする必要はありません。一方、STandのようなモナドIO(真の暗黙の可変状態)を使用すると、単にステートフルなプログラミング行うだけであり、単体テストは他のどの言語よりも簡単ではありません。
Derek Elkinsが

10
可変状態を必要とするオブジェクト指向の定義には何もありません。純粋に参照透過なOOコードを書くことは完全に可能です(そして広く行われています)。
イェルクWミッターク

1
関数型プログラミングに関連するすべての手法は、オブジェクト指向プログラミングに完全に適用できます。適切に設計されたオブジェクト指向プログラムは、通常、最低でも最低レベルで機能的手法を使用します。
フランクヒルマン2017年

1
@Fabioその場合は、FPプログラミングを使用する方が理にかなっています。オブジェクト指向は変更可能な状態を必要としませんが、変更可能性は、先ほど示した例では暗黙的です。同じことがf#にも当てはまります。ooのニーズに合わせて変数を可変にマークできます。ただし、変更可能な変数が多すぎる場合は、c#を使用することもできます。

回答:


17

オブジェクト指向プログラミングにアプローチする2つの異なる方法を区別したい

  1. シミュレーション担当者:オブジェクトは実際のドメインオブジェクトを表し、そのドメインに関連する機能を処理するようにプログラムしました。この方法でプログラムされたオブジェクトは、この機能を実装するために使用される多くの変更可能な状態と非表示の共同作業者を持つ可能性があります。
  2. レコード+関数:オブジェクトは、データとそのデータを操作する関数の単なるバンドルです。この方法でプログラムされたオブジェクトは、不変になりやすく、より少ない責任を引き受け、コラボレーターを注入できるようになります。

経験則では、最初の方法でプログラムされたオブジェクトにvoidは、2番目の方法よりも多くのメソッドとメソッドが含まれます。フライトシミュレータを作成し、飛行機のクラスを設計するとします。私たちは次のようなものになるでしょう:

class Plane {
    void accelerate();
    void deccelerate();
    void toggleRightFlaps();
    void toggleLeftFlaps();
    void turnRudderRight();
    void turnRudderLeft();
    void deployLandingGear();
    void liftLandingGear();
    // etc.
    void tick() throws PlaneCrashedException;
}

これはおそらく人が遭遇するよりも少し極端かもしれませんが、それは全体的に重要です。この種のインターフェースを実装したい場合は、オブジェクト内に保持する必要があります。

  1. 飛行機の機器の状態に関するすべての情報。
  2. 飛行機の速度/加速度に関するすべての情報。
  3. シミュレーションのリフレッシュレート(ティックを実装するため)。
  4. シミュレーションの3Dモデルとティックを実装するための物理に関する詳細。

モードで記述されたオブジェクトの単体テストを記述することは、次の理由により非常に困難です。

  1. テストの開始時に、このオブジェクトに必要なさまざまなデータとコラボレーターのすべてを提供する必要があります(これらを初期化するのは非常に面倒な場合があります)。
  2. メソッドをテストする場合、次の2つの問題が発生します。a)インターフェースがテストするのに十分なデータを公開していないことが多い(そのため、モック/リフレクションを使用して期待を検証する必要がある)b)多くのコンポーネントがバインドされている各テストで動作を確認する必要があるものに。

基本的には、ある程度合理的でドメインによく似ているようなインターフェースから始めますが、シミュレーションの素晴らしさにより、テストが非常に難しいオブジェクトを作成するようになっています。

ただし、同じ目的を満たすオブジェクトを作成できます。あなたはPlaneより小さなビットにあなたのブレーキをかけたいでしょう。有するPlaneParticle露光、1つは、これらを操作することができ、平面のphysicsyビット、位置、速度、加速度、ロール、ヨー、等、等を追跡しています。その後、PlanePartsオブジェクトはステータスを追跡できます。あなたはtick()完全に異なる場所に出荷しPlanePhysicsます。たとえば、与えられたa PlaneParticlePlaneParts新しいを吐き出す方法を知っている重力などによってパラメータ化されたオブジェクトがあるとしますPlaneParticle。これは、必ずしも例である必要はありませんが、すべて不変である可能性があります。

これで、テストに関して次の利点があります。

  1. 個々のコンポーネントは実行する必要が少なく、設定が簡単です。
  2. コンポーネントを個別にテストできます。
  3. これらのオブジェクトは内部を公開することで回避できるため(特に、オブジェクトが不変にされている場合)、それらを測定するために賢い必要はありません。

これが秘訣です。私が説明した2番目のオブジェクト指向アプローチは、関数型プログラミングに非常に近いものです。多分純粋な関数型プログラムでは、レコードと関数は別々であり、オブジェクトに結合されていません。関数型プログラムはこれらすべてのことを確実にします。ユニットテストが本当に簡単になると私が思うのは

  1. 小さなユニット(小さな状態空間)。
  2. 入力が最小限の関数(非表示の入力なし)。
  3. 最小限の出力で機能します。

関数型プログラミングはこれらのことを奨励します(ただし、どのパラダイムでも悪いプログラムを書くことができます)が、オブジェクト指向プログラムでは実現可能です。さらに、関数型プログラミングとオブジェクト指向プログラミングには互換性がないわけではないことを強調しておきます。


2
これが良い区別かどうかはわかりません。アプローチ1を使用する場合でも、多くの場合、アプローチ2で説明する手法は標準的なプラクティスであると見なされます。2つのアプローチは相互に排他的ではありません。平面のビットを組み合わせて、より大きな抽象化を作成できます。これで、アプローチ1の領域に入りました。アプローチ1については、説明したとおり、論理的ではありません。
フランクヒルマン2017年

3
「関数型プログラミングとオブジェクト指向プログラミングには互換性がないことも強調しておきます。」:オブジェクト指向プログラミングは動的なディスパッチのみが特徴であると言う人もいます。したがって、命令型 OOPスタイル:可変オブジェクト+動的ディスパッチ、または関数型 OOPスタイル:不変オブジェクト+動的ディスパッチで記述できます。
ジョルジオ

3
1つの大きなオブジェクトを小さな部分に分割することは、関数型プログラミングではありません。そして、大きなオブジェクトへの解決策はありません、データからロジックを分離し、それが貧血にする機能ですしかし、あなたの全体を誤解しているかもしれません。
Chris Wohlert、2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.