493

抽象クラスと抽象クラスを拡張するクラスを単体テストする方法を知りたいと思っていました。

抽象クラスを拡張してテストし、抽象メソッドをスタブ化してから、すべての具象メソッドをテストする必要がありますか? 次に、オーバーライドするメソッドのみをテストし、抽象クラスを拡張するオブジェクトの単体テストで抽象メソッドをテストしますか?

抽象クラスのメソッドをテストするために使用できる抽象テスト ケースを用意し、抽象クラスを拡張するオブジェクトのテスト ケースでこのクラスを拡張する必要がありますか?

私の抽象クラスには具体的なメソッドがいくつかあることに注意してください。

4

14 に答える 14

489

抽象基本クラスを使用する方法は 2 つあります。

  1. 抽象オブジェクトを特殊化していますが、すべてのクライアントはその基本インターフェイスを介して派生クラスを使用します。

  2. 設計内のオブジェクト内の重複を除外するために抽象基本クラスを使用しており、クライアントは独自のインターフェイスを介して具象実装を使用しています。


1の解決策 - 戦略パターン

オプション1

最初の状況の場合、実際には、派生クラスが実装している抽象クラスの仮想メソッドによって定義されたインターフェイスがあります。

これを実際のインターフェイスにして、抽象クラスを具象クラスに変更し、コンストラクターでこのインターフェイスのインスタンスを取得することを検討する必要があります。派生クラスは、この新しいインターフェイスの実装になります。

Iモーター

これは、新しいインターフェイスのモック インスタンスを使用して以前の抽象クラスをテストし、新しいパブリック インターフェイスを介してそれぞれの新しい実装をテストできることを意味します。すべてがシンプルでテスト可能です。


ソリューション フォー 2

2 番目の状況の場合、抽象クラスはヘルパー クラスとして機能しています。

抽象ヘルパー

含まれている機能を見てみましょう。この重複を最小限に抑えるために、操作されているオブジェクトにそれらのいずれかをプッシュできるかどうかを確認してください。まだ何か残っている場合は、具体的な実装がコンストラクターで受け取り、基本クラスを削除するヘルパー クラスにすることを検討してください。

モーターヘルパー

これも、単純で簡単にテストできる具体的なクラスにつながります。


原則として

複雑なオブジェクトの単純なネットワークよりも、単純なオブジェクトの複雑なネットワークを優先します。

拡張可能でテスト可能なコードの鍵は、小さな構成要素と独立した配線です。


更新: 両方の混合物を処理するには?

これらの両方の役割を実行する基本クラスを持つことが可能です...つまり、パブリック インターフェイスがあり、保護されたヘルパー メソッドがあります。この場合、ヘルパー メソッドを 1 つのクラス (シナリオ 2) に分解し、継承ツリーを戦略パターンに変換できます。

基本クラスが直接実装するメソッドがいくつかあり、他のメソッドが仮想であることがわかった場合でも、継承ツリーを戦略パターンに変換できますが、責任が正しく調整されていないことを示す良い指標としても捉えます。リファクタリングが必要です。


Update 2 : 踏み台としての抽象クラス (2014/06/12)

先日abstractを使った場面があったので、その理由を探ってみたいと思います。

設定ファイルには標準フォーマットがあります。この特定のツールには、すべてその形式の 3 つの構成ファイルがあります。設定ファイルごとに厳密に型指定されたクラスが必要だったので、依存性注入を通じて、クラスが関心のある設定を要求できるようになりました。

これを実装するには、設定ファイルの形式を解析する方法を知っている抽象基本クラスと、同じメソッドを公開するが設定ファイルの場所をカプセル化する派生クラスを用意しました。

3 つのクラスをラップした "SettingsFileParser" を作成し、基本クラスに委譲してデータ アクセス メソッドを公開することもできました。私はまだこれを行わないことを選択しました。これは、他の何よりも多くの委譲コードを含む 3 つの派生クラスにつながるためです。

ただし...このコードが進化するにつれて、これらの各設定クラスの消費者がより明確になります。各設定のユーザーは、いくつかの設定を要求し、何らかの方法で変換します (設定はテキストであるため、それらを数値に変換するオブジェクトでラップする場合があります)。これが発生したら、このロジックをデータ操作メソッドに抽出し、それらを厳密に型指定された設定クラスにプッシュします。これにより、設定の各セットのより高いレベルのインターフェイスにつながり、最終的には「設定」を扱っていることに気付かなくなります。

この時点で、厳密に型指定された設定クラスは、基になる「設定」の実装を公開する「ゲッター」メソッドを必要としなくなります。

その時点で、パブリック インターフェイスに設定アクセサー メソッドを含めたくありません。そのため、このクラスを変更して、設定パーサー クラスから派生させるのではなく、カプセル化します。

したがって、Abstract クラスは、現時点では委譲コードを回避する方法であり、後で設計を変更することを思い出させるためのコード内のマーカーです。私はそれにたどり着かないかもしれないので、それはしばらく続くかもしれません...コードだけがそれを知ることができます。

これは、「静的メソッドなし」や「プライベートメソッドなし」など、どのルールにも当てはまります。それらはコードの臭いを示しています...そしてそれは良いことです。見落としていた抽象化を探し続け、その間に顧客に価値を提供し続けることができます。

保守可能なコードが谷に住んでいる風景を定義するこのようなルールを想像します。新しい動作を追加すると、コードに雨が降るようになります。最初は、着地する場所に配置します..次に、リファクタリングして、すべてが谷に行き着くまで、優れた設計の力が動作をプッシュできるようにします。

于 2010-06-01T07:01:06.243 に答える
282

モックオブジェクトを作成し、テストのためだけに使用します。それらは通常、非常に最小限(抽象クラ​​スから継承)であり、それ以上ではありません。次に、単体テストで、テストする抽象メソッドを呼び出すことができます。

他のすべてのクラスと同様に、いくつかのロジックを含む抽象クラスをテストする必要があります。

于 2008-10-28T13:50:25.077 に答える
12

抽象クラスとインターフェースに対して私が行うことは次のとおりです。具体的なオブジェクトを使用するテストを作成します。ただし、タイプX(Xは抽象クラス)の変数はテストで設定されていません。このテストクラスはテストスイートに追加されませんが、そのサブクラスには、変数をXの具体的な実装に設定するsetup-methodがあります。これにより、テストコードを複製しません。未使用のテストのサブクラスは、必要に応じてさらにテストメソッドを追加できます。

于 2008-10-28T13:47:34.723 に答える
10

特に抽象クラスで単体テストを行うには、テスト目的、base.method()の結果のテスト、および継承時の意図された動作のために単体テストを派生させる必要があります。

メソッドを呼び出すことでテストするので、実装して抽象クラスをテストします...

于 2008-10-28T13:39:22.667 に答える
9

抽象クラスにビジネス価値のある具体的な機能が含まれている場合は、通常、抽象データをスタブ化するテストダブルを作成するか、モックフレームワークを使用してこれを行うことで直接テストします。どちらを選択するかは、抽象メソッドのテスト固有の実装を作成する必要があるかどうかに大きく依存します。

これを行う必要がある最も一般的なシナリオは、サードパーティによって使用されるある種の拡張可能なフレームワークを構築している場合など、テンプレートメソッドパターンを使用している場合です。この場合、抽象クラスは私がテストしたいアルゴリズムを定義するものなので、特定の実装よりも抽象ベースをテストする方が理にかなっています。

ただし、これらのテストでは、実際のビジネスロジックの具体的な実装のみに焦点を当てることが重要だと思います。脆弱なテストになってしまうため、抽象クラスの実装テストの詳細を単体テストしないでください。

于 2008-10-28T14:19:18.250 に答える
6

1つの方法は、抽象クラスに対応する抽象テストケースを作成してから、抽象テストケースをサブクラス化する具体的なテストケースを作成することです。これは、元の抽象クラスの具象サブクラスごとに行います(つまり、テストケース階層はクラス階層を反映しています)。junitレシピブックのインターフェースのテスト: http://safari.informit.com/9781932394238/ch02lev1sec6を参照してください。サファリアカウントをお持ちでない場合は、 https ://www.manning.com/books/junit-recipesまたはhttps://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/1932394230 。

xUnitパターンのテストケーススーパークラスも参照してください: http://xunitpatterns.com/Testcase%20Superclass.html

于 2008-12-04T07:41:05.363 に答える
4

私は「抽象的な」テストに反対します。テストは具体的なアイデアであり、抽象化されていないと思います。共通の要素がある場合は、それらをヘルパーメソッドまたはクラスに入れて、全員が使用できるようにします。

抽象テストクラスのテストについては、何をテストしているのかを自問してください。いくつかのアプローチがあり、シナリオで何が機能するかを確認する必要があります。サブクラスで新しいメソッドをテストしようとしていますか?次に、テストがそのメソッドとのみ相互作用するようにします。基本クラスのメソッドをテストしていますか?次に、おそらくそのクラス専用の個別のフィクスチャを用意し、必要な数のテストを使用して各メソッドを個別にテストします。

于 2008-10-28T13:35:55.013 に答える
4

これは、抽象クラスをテストするためのハーネスをセットアップするとき、私が通常従うパターンです。

public abstract class MyBase{
  /*...*/
  public abstract void VoidMethod(object param1);
  public abstract object MethodWithReturn(object param1);
  /*,,,*/
}

そして、私がテストで使用するバージョン:

public class MyBaseHarness : MyBase{
  /*...*/
  public Action<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

予期しないときに抽象メソッドが呼び出されると、テストは失敗します。テストを配置するとき、アサートを実行したり、例外をスローしたり、さまざまな値を返したりするラムダを使用して、抽象メソッドを簡単にスタブ化できます。

于 2008-10-28T14:03:46.703 に答える
3

具体的なメソッドがその戦略が機能しない抽象メソッドのいずれかを呼び出す場合、各子クラスの動作を個別にテストする必要があります。それ以外の場合は、抽象クラスの具象メソッドが子クラスから分離されていれば、それを拡張して、説明したように抽象メソッドをスタブ化することは問題ありません。

于 2008-10-28T13:36:38.787 に答える
2

抽象クラスを使用する主な動機の1つは、アプリケーション内でポリモーフィズムを有効にすることです。つまり、実行時に別のバージョンに置き換えることができます。実際、これは、抽象クラスがテンプレートパターンと呼ばれることが多い一般的な配管を提供することを除いて、インターフェイスを使用する場合とほとんど同じです。

ユニットテストの観点から、考慮すべき2つのことがあります。

  1. 抽象クラスとそれに関連するクラスとの相互作用。模擬テストフレームワークを使用すると、抽象クラスが他のクラスとうまく機能することが示されるため、このシナリオには理想的です。

  2. 派生クラスの機能。派生クラス用に作成したカスタムロジックがある場合は、それらのクラスを個別にテストする必要があります。

編集:RhinoMocksは、クラスから動的に派生することにより、実行時にモックオブジェクトを生成できる素晴らしいモックテストフレームワークです。このアプローチにより、派生クラスを手動でコーディングする時間を数え切れないほど節約できます。

于 2008-10-28T14:09:27.503 に答える
2

抽象クラスの基本機能をテストしたいと思うかもしれません...しかし、メソッドをオーバーライドせずにクラスを拡張し、抽象メソッドのモックを最小限に抑えることをお勧めします。

于 2008-10-28T13:36:50.863 に答える