NUnit - 特定のインターフェイスを実装するすべてのクラスをテストする方法
-
09-06-2019 - |
質問
インターフェイス IFoo があり、それを実装するクラスがいくつかある場合、それらすべてのクラスをインターフェイスに対してテストするための最良/最もエレガント/賢明な方法は何ですか?
テストコードの重複を減らしたいと考えていますが、それでも単体テストの原則に「忠実であり続けたい」と考えています。
ベストプラクティスは何だと思いますか?NUnit を使用していますが、単体テスト フレームワークの例は有効だと思います
解決
クラスが 1 つのインターフェイスを実装している場合は、すべてのクラスがそのインターフェイスのメソッドを実装する必要があります。これらのクラスをテストするには、クラスごとに単体テスト クラスを作成する必要があります。
代わりに、よりスマートなルートを選択しましょう。あなたの目標が次の場合 コードとテストコードの重複を避ける 代わりに、 繰り返しの コード。
例えば。次のインターフェイスがあります。
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());
}
}
インターフェイスが暗黙的に示す共通コードを抽象クラスに処理させると、よりクリーンなコード設計が得られます。
これが理解できることを願っています。
余談ですが、これは、と呼ばれる一般的なデザイン パターンです。 テンプレートメソッドパターン. 。上記の例では、テンプレート メソッドは CommonCode
方法と SpecificCode
スタブまたはフックと呼ばれます。舞台裏のことを知らなくても、誰でも動作を拡張できるという考えです。
多くのフレームワークはこの動作パターンに依存しています。 ASP.NET ページまたは生成されたユーザー コントロールにフックを実装する必要がある場合 Page_Load
によって呼び出されるメソッド Load
イベントが発生すると、テンプレート メソッドがバックグラウンドでフックを呼び出します。この例は他にもたくさんあります。基本的に、「load」、「init」、または「render」という単語を使用して実装する必要があるものはすべて、テンプレート メソッドによって呼び出されます。
他のヒント
私は同意しません ジョン・リムジャップ 彼が言うと、
これは、a.) メソッドがどのように実装されるべきか、b.) そのメソッドが正確に何を行うべきか (戻り値の型を保証するだけ) についての契約ではありません。私が収集した 2 つの理由が、この種のメソッドを望む動機になります。テストの。
コントラクトの多くの部分が戻り値の型で指定されていない可能性があります。言語に依存しない例:
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、その他すべての単体テストでは、リスト自体が返されるだけでなく、リストが適切に変更されていることもテストする必要があります。
がありました 前の質問 これは、これらのテストを完了する 1 つの方法で正しい方向を示すのに役立ちます。
契約のオーバーヘッドを望まない場合は、次のようなテスト リグをお勧めします。 スポーク 推奨:
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.) そのメソッドが正確に何を行うべきか (戻り値の型のみを保証する) のいずれかに関する契約。私が収集した 2 つの理由が、この種のテストを希望する動機になります。
メソッドの実装を本当に制御したい場合は、次のオプションがあります。
- 抽象クラスのメソッドとして実装し、それを継承します。これを具象クラスに継承する必要がありますが、明示的にオーバーライドしない限り、そのメソッドが正しい動作を行うことは確実です。
- .NET 3.5/C# 3.0では、インターフェイスを参照する拡張メソッドとしてメソッドを実装します。
例:
public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
//method body goes here
}
その拡張メソッドを適切に参照する実装は、その拡張メソッドを正確に出力するため、テストする必要があるのは 1 回だけです。
@皇帝42世
私は MbUnit の組み合わせテストのサウンドが好きです。NUnit で抽象基本クラス インターフェイス テスト手法を試しました。これは機能しますが、クラスが実装するインターフェイスごとに個別のテスト フィクスチャが必要です (C# の場合以来)。多重継承はありません - 内部クラスは使用できますが、これは非常に優れています)。実際には、これは問題なく、実装クラスのテストをインターフェイスごとにグループ化するため、おそらく有利ですらあります。しかし、フレームワークがもっと賢くなれば良いのにと思います。属性を使用してクラスをインターフェイスの「公式」テスト クラスとしてマークできれば、フレームワークはテスト対象のアセンブリでそのインターフェイスを実装するすべてのクラスを検索し、そのアセンブリに対してテストを実行します。
それはクールですね。
[TestFixture] のクラスの階層はどうでしょうか?共通テスト コードを基本テスト クラスに配置し、子テスト クラスに継承します。
インターフェイスまたは基本クラスのコントラクトをテストするときは、テスト フレームワークにすべての実装者の検索を自動的に行わせることを好みます。これにより、多くの手動実装を行わなくても、テスト対象のインターフェイスに集中でき、すべての実装がテストされることを合理的に確認できます。
- のために xUnit.net, を作成しました。 タイプリゾルバ ライブラリを使用して、特定の型のすべての実装を検索します (xUnit.net 拡張機能は、タイプ リゾルバー機能の薄いラッパーにすぎないため、他のフレームワークでの使用に適合させることができます)。
- で MbUnit, を使用できます。
CombinatorialTest
とUsingImplementations
パラメータの属性。 - 他のフレームワークの場合、基本クラスのパターン スポーク 言及されたものは役立つ可能性があります。
インターフェイスの基本をテストするだけでなく、個々の実装が特定の要件に従っていることもテストする必要があります。
NUnit は使用しませんが、C++ インターフェイスをテストしました。まず、その基本的な実装である TestFoo クラスをテストして、一般的なものが機能することを確認します。次に、各インターフェイスに固有のものをテストするだけです。