回答:
Mockオブジェクトを記述して、テストのためだけに使用します。通常、これらは非常に非常に最小限(抽象クラスから継承)であり、それ以上ではありません。次に、単体テストで、テストする抽象メソッドを呼び出すことができます。
あなたが持っている他のすべてのクラスのようないくつかのロジックを含む抽象クラスをテストする必要があります。
抽象基本クラスを使用する方法は2つあります。
あなたは抽象オブジェクトを特化していますが、すべてのクライアントはその基本インターフェイスを通じて派生クラスを使用します。
抽象基本クラスを使用して、デザイン内のオブジェクト内の重複を除外し、クライアントは独自のインターフェイスを通じて具体的な実装を使用します。
1のソリューション-戦略パターン
最初の状況の場合、実際には、派生クラスが実装している抽象クラスの仮想メソッドによって定義されたインターフェースがあります。
これを実際のインターフェースにして、抽象クラスを具象に変更することを検討し、コンストラクタでこのインターフェースのインスタンスを取得する必要があります。派生クラスは、この新しいインターフェースの実装になります。
つまり、新しいインターフェースのモックインスタンスを使用して、以前は抽象クラスをテストし、現在はパブリックインターフェースを介して新しい実装をテストできます。すべてがシンプルでテスト可能です。
2のソリューション
2番目の状況がある場合、抽象クラスはヘルパークラスとして機能しています。
含まれている機能を見てください。この重複を最小限に抑えるために、操作されているオブジェクトにプッシュできるかどうかを確認してください。まだ何か残っている場合は、それをヘルパークラスにして、具体的な実装がコンストラクターで受け取り、基本クラスを削除することを検討してください。
これもまた、シンプルで簡単にテストできる具象クラスにつながります。
原則として
複雑なオブジェクトの単純なネットワークよりも、単純なオブジェクトの複雑なネットワークを優先します。
拡張可能なテスト可能なコードの鍵は、小さなビルディングブロックと独立した配線です。
更新:両方の混合を処理する方法?
これらの両方の役割を実行する基本クラスを持つことは可能です...つまり:パブリックインターフェイスを持ち、ヘルパーメソッドを保護しています。この場合は、ヘルパーメソッドを1つのクラス(scenario2)に分解して、継承ツリーを戦略パターンに変換できます。
基本クラスが直接実装するメソッドがあり、他のメソッドが仮想であることがわかった場合でも、継承ツリーを戦略パターンに変換できますが、責任が正しく調整されておらず、リファクタリングが必要です。
Update 2:飛び石としての抽象クラス(2014/06/12)
先日、アブストラクトを使っていたので、その理由を探っていきたいです。
構成ファイルの標準形式があります。この特定のツールには、すべてその形式の3つの構成ファイルがあります。各設定ファイルに強く型付けされたクラスが必要だったので、依存性注入により、クラスはそれが必要とする設定を要求できました。
これを実装するには、設定ファイルの形式を解析する方法を知っている抽象基本クラスと、同じメソッドを公開するが、設定ファイルの場所をカプセル化する派生クラスを用意しました。
3つのクラスがラップする「SettingsFileParser」を作成し、基本クラスに委任してデータアクセスメソッドを公開することもできます。他の何よりも多くの委任コードを含む3つの派生クラスにつながるため、私はまだこれを行わないことを選択しました。
しかし...このコードが進化し、これらの各設定クラスのコンシューマーがより明確になるにつれて。各設定ユーザーは、いくつかの設定を要求し、それらを何らかの方法で変換します(設定はテキストであるため、数値に変換するオブジェクトにラップする場合があります)。これが発生すると、このロジックをデータ操作メソッドに抽出し、厳密に型指定された設定クラスにプッシュし直します。これにより、設定の各セットのより高いレベルのインターフェースにつながり、最終的には「設定」を処理していることに気付かなくなります。
この時点で、強く型付けされた設定クラスは、基になる「設定」実装を公開する「getter」メソッドを必要としなくなります。
その時点で、私はもうパブリックインターフェイスに設定アクセサーメソッドを含めたくありません。したがって、このクラスを変更して、設定パーサークラスを派生させるのではなくカプセル化します。
したがって、Abstractクラスは次のとおりです。現時点で委任コードを回避する方法と、後でデザインを変更するように通知するコード内のマーカー。私はそれに到達できないかもしれないので、それは良い人生を送るかもしれません...コードだけが伝えることができます。
これは、「静的メソッドなし」や「プライベートメソッドなし」などのルールに当てはまることがわかります。それらはコードのにおいを示しています...そしてそれは良いことです。それはあなたが見逃してしまった抽象化を探し続ける...そして、あなたがその間に顧客に価値を提供し続けることを可能にします。
このようなルールは、メンテナンス可能なコードが谷にあるランドスケープを定義していると思います。新しい動作を追加すると、コードに雨が降るようなものです。最初は着地する場所に配置します。次に、リファクタリングを行って、優れた設計の力がすべての谷に到達するまで動作を押し進めます。
抽象クラスとインターフェイスに対して私が行うことは次のとおりです。具体的にオブジェクトを使用するテストを記述します。ただし、タイプX(Xは抽象クラス)の変数はテストで設定されていません。このテストクラスはテストスイートには追加されませんが、そのサブクラスであり、変数をXの具体的な実装に設定するsetupメソッドを持っています。このようにして、テストコードを複製しません。未使用のテストのサブクラスは、必要に応じてテストメソッドを追加できます。
抽象クラスで具体的にユニットテストを行うには、テスト目的でユニットテストを派生させ、base.method()の結果と継承時の意図した動作をテストする必要があります。
メソッドを呼び出すことでテストするので、実装して抽象クラスをテストします...
抽象クラスにビジネス価値のある具体的な機能が含まれている場合は、通常、抽象データをスタブするテストダブルを作成するか、モックフレームワークを使用してこれを直接テストします。どちらを選択するかは、抽象メソッドのテスト固有の実装を作成する必要があるかどうかに大きく依存します。
これを行う必要がある最も一般的なシナリオは、テンプレートメソッドパターンを使用している場合です。、サードパーティが使用する拡張可能なフレームワークを構築しているときなどいるときです。この場合、抽象クラスは、テストするアルゴリズムを定義するものであるため、特定の実装よりも抽象ベースをテストする方が理にかなっています。
ただし、これらのテストでは、実際のビジネスロジックの具体的な実装のみに焦点を当てることが重要であると思います。抽象クラスの実装の詳細を単体テストしないでください。脆弱なテストが発生するためです。
1つの方法は、抽象クラスに対応する抽象テストケースを記述してから、抽象テストケースをサブクラス化する具体的なテストケースを記述することです。これを、元の抽象クラスの具象サブクラスごとに実行します(つまり、テストケース階層はクラス階層を反映しています)。junit recipiesブックのインターフェースのテストを参照してください:http ://safari.informit.com/9781932394238/ch02lev1sec6。
xUnitパターンのTestcaseスーパークラスも参照してください:http ://xunitpatterns.com/Testcase%20Superclass.html
私は「抽象的な」テストに反対するでしょう。テストは具体的なアイデアであり、抽象化されていないと思います。共通の要素がある場合は、それらをヘルパーメソッドまたはクラスに配置して、全員が使用できるようにします。
抽象テストクラスのテストについては、それが何をテストしているのかを自問してください。いくつかのアプローチがあり、シナリオで何が機能するかを確認する必要があります。サブクラスで新しいメソッドをテストしようとしていますか?次に、テストがそのメソッドとのみ対話するようにします。基本クラスのメソッドをテストしていますか?次に、おそらくそのクラス専用の個別のフィクスチャを用意し、必要なだけ多くのテストを行って、各メソッドを個別にテストします。
これは、抽象クラスをテストするためのハーネスを設定するときに私が通常従うパターンです。
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);
}
/*,,,*/
}
予期しないときに抽象メソッドが呼び出されると、テストは失敗します。テストをアレンジするとき、アサートを実行したり、例外をスローしたり、さまざまな値を返したりするラムダを使用して、抽象メソッドを簡単に作成できます。
抽象クラスを使用する主な動機の1つは、アプリケーション内でポリモーフィズムを有効にすることです。つまり、実行時に別のバージョンに置き換えることができます。実際、これはインターフェースを使用することとほとんど同じですが、抽象クラスがいくつかの一般的な配管を提供する点が異なります。これは、しばしばテンプレートパターンと呼ばれます。
単体テストの観点から、考慮すべき2つの点があります。
抽象クラスとそれに関連するクラスの相互作用。抽象クラスが他のクラスとうまく機能することが示されているため、模擬テストフレームワークの使用はこのシナリオに最適です。
派生クラスの機能。派生クラス用に作成したカスタムロジックがある場合は、それらのクラスを個別にテストする必要があります。
編集:RhinoMocksは、クラスから動的に派生させることで実行時にモックオブジェクトを生成できる素晴らしいモックテストフレームワークです。このアプローチにより、派生クラスを手作業でコーディングする時間を無数に節約できます。
まず、抽象クラスに具体的なメソッドが含まれている場合、この例を考慮してこれを行う必要があると思います
public abstract class A
{
public boolean method 1
{
// concrete method which we have to test.
}
}
class B extends class A
{
@override
public boolean method 1
{
// override same method as above.
}
}
class Test_A
{
private static B b; // reference object of the class B
@Before
public void init()
{
b = new B ();
}
@Test
public void Test_method 1
{
b.method 1; // use some assertion statements.
}
}
抽象クラスが実装に適している場合は、(上記のように)派生した具象クラスをテストします。あなたの仮定は正しいです。
将来の混乱を避けるために、この具象テストクラスはモックではなく、偽物であることに注意してください。
厳密に言うと、モックは次の特性によって定義されます。
@ patrick-desjardinsの回答に続いて@Test
、次のように抽象クラスとその実装クラスを実装しました。
抽象クラス-ABC.java
import java.util.ArrayList;
import java.util.List;
public abstract class ABC {
abstract String sayHello();
public List<String> getList() {
final List<String> defaultList = new ArrayList<>();
defaultList.add("abstract class");
return defaultList;
}
}
抽象クラスはインスタンス化することはできませんが、彼らはサブクラス化することができ、具体的なクラスDEF.javaは次のように、次のとおりです。
public class DEF extends ABC {
@Override
public String sayHello() {
return "Hello!";
}
}
抽象メソッドと非抽象メソッドの両方をテストする@Testクラス:
import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;
public class DEFTest {
private DEF def;
@Before
public void setup() {
def = new DEF();
}
@Test
public void add(){
String result = def.sayHello();
assertThat(result, is(equalTo("Hello!")));
}
@Test
public void getList(){
List<String> result = def.getList();
assertThat((Collection<String>) result, is(not(empty())));
assertThat(result, contains("abstract class"));
}
}