インターフェイス IFoo があり、それを実装するいくつかのクラスがある場合、これらすべてのクラスをインターフェイスに対してテストするための最良/最もエレガント/最も賢い方法は何ですか?
テスト コードの重複を減らしたいと考えていますが、ユニット テストの原則には「忠実であり続ける」必要があります。
ベストプラクティスは何だと思いますか? 私はNUnitを使用していますが、ユニットテストフレームワークの例は有効だと思います
インターフェイス IFoo があり、それを実装するいくつかのクラスがある場合、これらすべてのクラスをインターフェイスに対してテストするための最良/最もエレガント/最も賢い方法は何ですか?
テスト コードの重複を減らしたいと考えていますが、ユニット テストの原則には「忠実であり続ける」必要があります。
ベストプラクティスは何だと思いますか? 私はNUnitを使用していますが、ユニットテストフレームワークの例は有効だと思います
クラスにいずれかのインターフェイスを実装する場合、それらはすべてそのインターフェイスのメソッドを実装する必要があります。これらのクラスをテストするには、クラスごとに単体テスト クラスを作成する必要があります。
代わりに、よりスマートなルートを使用しましょう。コードを避けてコードの重複をテストすることが目標である場合は、繰り返しコードを処理する代わりに、抽象クラスを作成することをお勧めします。
たとえば、次のインターフェースがあります。
public interface IFoo {
public void CommonCode();
public void SpecificCode();
}
抽象クラスを作成したい場合があります。
public abstract class AbstractFoo : IFoo {
public void CommonCode() {
SpecificCode();
}
public abstract void SpecificCode();
}
簡単なテスト。テスト クラスに抽象クラスを内部クラスとして実装します。
[TestFixture]
public void TestClass {
private class TestFoo : AbstractFoo {
boolean hasCalledSpecificCode = false;
public void SpecificCode() {
hasCalledSpecificCode = true;
}
}
[Test]
public void testCommonCallsSpecificCode() {
TestFoo fooFighter = new TestFoo();
fooFighter.CommonCode();
Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
}
}
...または、それがあなたの好みに合っている場合は、テスト クラスに抽象クラス自体を拡張させます。
[TestFixture]
public void TestClass : AbstractFoo {
boolean hasCalledSpecificCode;
public void specificCode() {
hasCalledSpecificCode = true;
}
[Test]
public void testCommonCallsSpecificCode() {
AbstractFoo fooFighter = this;
hasCalledSpecificCode = false;
fooFighter.CommonCode();
Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
}
}
インターフェースが暗示する共通コードを抽象クラスが処理することで、よりクリーンなコード設計が実現します。
これがあなたにとって理にかなっていることを願っています。
ちなみに、これはTemplate Method パターンと呼ばれる一般的な設計パターンです。上記の例では、テンプレート メソッドはCommonCode
メソッドでありSpecificCode
、スタブまたはフックと呼ばれます。アイデアは、舞台裏のことを知る必要なく、誰でも動作を拡張できるということです。
多くのフレームワークは、この動作パターンに依存しています。たとえば、イベントによって呼び出される生成されたメソッドなどのページまたはユーザー コントロールにフックを実装する必要があるASP.NETでは、テンプレート メソッドがバックグラウンドでフックを呼び出します。これにはさらに多くの例があります。基本的に、「load」、「init」、または「render」という言葉を使用して実装する必要があるものはすべて、テンプレート メソッドによって呼び出されます。Page_Load
Load
Jon Limjap の次のような意見には同意しません。
これは、a.) メソッドをどのように実装するか、b.) そのメソッドが正確に何をすべきか (戻り値の型のみを保証する) のいずれかに関する契約ではありません。テストの。
戻り型で指定されていないコントラクトの多くの部分が存在する可能性があります。言語に依存しない例:
public interface List {
// adds o and returns the list
public List add(Object o);
// removed the first occurrence of o and returns the list
public List remove(Object o);
}
LinkedList、ArrayList、CircularlyLinkedList、およびその他すべての単体テストでは、リスト自体が返されるだけでなく、それらが適切に変更されていることもテストする必要があります。
契約による設計に関する以前の質問がありました。これは、これらのテストを DRY する 1 つの方法で正しい方向を示すのに役立ちます。
コントラクトのオーバーヘッドが必要ない場合は、Spoikeが推奨する方法に沿って、テスト リグをお勧めします。
abstract class BaseListTest {
abstract public List newListInstance();
public void testAddToList() {
// do some adding tests
}
public void testRemoveFromList() {
// do some removing tests
}
}
class ArrayListTest < BaseListTest {
List newListInstance() { new ArrayList(); }
public void arrayListSpecificTest1() {
// test something about ArrayLists beyond the List requirements
}
}
これはベストプラクティスではないと思います。
単純な真実は、インターフェイスは、メソッドが実装されているという契約にすぎないということです。これは、a.) メソッドをどのように実装するか、b.) そのメソッドが正確に何をすべきか (戻り値の型のみを保証する) のいずれかに関する契約ではありません。テストの。
メソッドの実装を本当に管理したい場合は、次のオプションがあります。
例:
public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
//method body goes here
}
その拡張メソッドを適切に参照する実装はすべて、その拡張メソッドを正確に発行するため、一度テストするだけで済みます。
[TestFixture] のクラスの階層はどうですか? 共通のテスト コードをベース テスト クラスに配置し、それを子テスト クラスに継承します。
インターフェイスまたは基本クラスのコントラクトをテストするときは、テスト フレームワークが自動的にすべての実装者を見つけられるようにすることを好みます。これにより、多くの手動実装を行う必要なく、テスト中のインターフェイスに集中し、すべての実装がテストされることを合理的に確信できます。
CombinatorialTest
とともにa を使用できます。UsingImplementations
インターフェイスの基本をテストするだけでなく、個々の実装が特定の要件に従っていることもテストする必要があります。
NUnit は使用しませんが、C++ インターフェイスはテスト済みです。最初に、一般的なものが機能することを確認するために、その基本的な実装である TestFoo クラスをテストします。次に、各インターフェイスに固有のものをテストするだけです。