3

同じインターフェイスを実装する複数のクラスがあります。コードの重複を最小限に抑えて (DRY)、各クラスがインターフェイスを正しく実装していることを確認するには、どのように単体テストを記述すればよいですか?

IDeleter私が言いたいことの例として、以下は:Deleter1との 2 つの実装を含む非常に基本的なライブラリDeleter2です。どちらも、関連付けられDeleteた を呼び出すことによってメソッドを実装します。DeleteIRepository

using Microsoft.Practices.Unity;

namespace TestMultiple
{
    public interface IRepository
    {
        void Delete(string id);
    }

    public abstract class Baseclass
    {
        protected abstract IRepository GenericRepository { get; }

        public void Delete(string id)
        {
            GenericRepository.Delete(id);
        }
    }

    public interface IDeleter
    {
        void Delete(string id);
    }

    public interface IRepository1 : IRepository
    {
    }

    public abstract class RepositoryBase
    {
        public void Delete(string id)
        {
        }
    }

    public class Repository1 : RepositoryBase, IRepository1
    {
    }

    public class Deleter1 : Baseclass, IDeleter
    {
        protected override IRepository GenericRepository { get { return Repository; } }

        [Dependency]
        public IRepository1 Repository { get; set; }
    }

    public interface IRepository2 : IRepository
    {
    }

    public class Repository2 : RepositoryBase, IRepository2
    {
    }

    public class Deleter2 : Baseclass, IDeleter
    {
        protected override IRepository GenericRepository { get { return Repository; } }

        [Dependency]
        public IRepository2 Repository { get; set; }
    }
}

これら 2 つのクラスDeleter1とについてDeleter2、以下のスニペットに示すように、対応する 2 つの単体テスト クラスを作成しました。テストは、同じ動作をチェックします。つまりDelete、基礎となるリポジトリで呼び出されます。のすべての実装に対して同じテストを実装するより良い方法はありIDeleterますか? たとえば、TestDelete、 for 、 などの一般的なテスト メソッドを含む基底クラスを作成する必要がTestDeleter1ありTestDeleter2ますか?

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Practices.Unity;
using Moq;

namespace TestMultiple.Tests
{
    [TestClass]
    public class TestDeleter1
    {
        [TestMethod]
        public void TestDelete()
        {
            var mockRepo = new Mock<IRepository1>();
            var container = new UnityContainer().RegisterInstance<IRepository1>(mockRepo.Object);
            var deleter = container.Resolve<Deleter1>();
            deleter.Delete("id");
            mockRepo.Verify(r => r.Delete("id"));
        }
    }

    [TestClass]
    public class TestDeleter2
    {
        [TestMethod]
        public void TestDelete()
        {
            var mockRepo = new Mock<IRepository2>();
            var container = new UnityContainer().RegisterInstance<IRepository2>(mockRepo.Object);
            var deleter = container.Resolve<Deleter2>();
            deleter.Delete("id");
            mockRepo.Verify(r => r.Delete("id"));
        }
    }
}

編集: この種の問題を解決するのに役立つユニット テスト フレームワークについて自由に言及してください。ただし、私の好みは NUnit です。

4

3 に答える 3

3

私が知っているフレームワークでは、インターフェイスで共通の動作をアサートするテストを簡単に作成する方法はありません。できる最善の方法は、あたかも抽象クラスをテストしているかのようにテストとヘルパー メソッドを記述し、派生したテスト クラスに実際の型を挿入することです。

たとえばDeleterTests、インターフェイスのテストを提供するクラスを作成できます。

public abstract class DeleterTests<TRepository> where TRepository : IRepository
{
    [TestMethod]
    public void TestDelete()
    {
        var mockRepo = new Mock<TRepository>();
        var container = new UnityContainer();

        container.RegisterInstance<TRepository>(mockRepo.Object);

        var deleter = this.CreateDeleter(container);

        deleter.Delete("id");
        mockRepo.Verify(r => r.Delete("id"));
    }

    protected abstract IDeleter CreateDeleter(IUnityContainer container);
}

IDeleter次に、必要に応じて抽象CreateDeleterメソッドを実装し、実装するものについてこのクラスから継承します。

public class Deleter1Tests : DeleterTests<IRepository1>
{
    protected override IDeleter CreateDeleter(IUnityContainer container)
    {
        return container.Resolve<Deleter1>();
    }
}

public class Deleter2Tests: DeleterTests<IRepository2>
{
    protected override IDeleter CreateDeleter(IUnityContainer container)
    {
        return container.Resolve<Deleter2>();
    }
}

インスタンスを別の方法で構成する必要がある場合はCreateDeleter、任意の方法で抽象を実装できます。

于 2012-11-19T13:32:14.560 に答える
1

他の実装についてあまり気にせずに、クラスごとに単体テストを作成する必要があります。同じテストを何度も書いているような気がする場合、それはおそらく、テスト コードではなく、製品コードが非 DRY であることが原因です。他の人が指摘したように; 異なる実装に多くの共通点がある場合、いくつかの共通の抽象的な祖先はおそらく良い考えです。

于 2012-11-19T13:58:56.003 に答える
0

すべてのクラスが IDeleter インターフェイスを同じように実装する必要がある場合は、基本クラスを抽象化しないでください。すべての子クラスがベースから同じ実装を継承するように、ベース クラスに IDeleter インターフェイスを実装します。別の実装が必要なエッジ ケースがある場合、そのクラスは基本クラスの実装をオーバーライドできます。

于 2012-11-19T11:40:40.083 に答える