4

現時点では空き時間があるので、DI および IoC コンテナーについて頭を悩ませようとしています。私が unity を選んだ理由は、主要なフレームワーク間に大きな違いがなく、最初から心配しなければならないということ以外にはありません。後で物事がより複雑になると、変更が必要になる可能性があることに気付きますが、今のところはそれでうまくいくことを願っています.

したがって、私は比較的単純なデータ アクセス シナリオで作業しており、次のインターフェイスとデータ アクセス クラスを実装しています。

public interface IEventRepository
{
    IEnumerable<Event> GetAll();
}

public class EventRepository : IEventRepository
{
    public IEnumerable<Event> GetAll()
    {
        // Data access code here
    }
}

次に、使用するには、次のことができます。

IUnityContainer container = new UnityContainer();
container.RegisterType(typeof(IEventRepository), typeof(EventRepository));

var eventRepo = container.Resolve<IEventRepository>();
eventRepo.GetAll();

私が理解していることから 6 か月でデータベース プロバイダーを変更する必要がある場合は、IEventRepository の新しい実装を作成し、型の登録を更新します。それで問題ありません。

さて、ここで迷っています。たとえば、キャッシングを実装したい場合は、IEventRepository の適切な実装から継承し、適切なメソッドをオーバーライドして、必要なキャッシングを実装できます。ただし、このようにすると、DI を介して渡された Moq 実装を使用してキャッシングが正しく機能しているかどうかをテストするのが難しくなるため、DI の真の精神では、IEventRepository の実装を作成し、DI を使用してこんな感じでIEventRepositoryの実データアクセス実装。

public class CachedEventRepository : IEventRepository
{
    private readonly IEventRepository _eventRepo;

    public CachedEventRepository(IEventRepository eventRepo)
    {
        if (eventRepo is CachedEventRepository)
            throw new ArgumentException("Cannot pass a CachedEventRepository to a CachedEventRepository");

        _eventRepo = eventRepo;
    }

    public IEnumerable<Event> GetAll()
    {
        // Appropriate caching code ultimately calling _eventRepo.GetAll() if needed
    }
}

これは理にかなっていますか、それとも私はこれについてすべて間違っていますか? 何を提案しますか?正しく実行している場合、CachedEventRepository が IEventRepository の適切なデータ アクセス実装を取得するように、次の状況を解決するにはどうすればよいですか?

IUnityContainer container = new UnityContainer();
container.RegisterType(typeof(IEventRepository), typeof(EventRepository));
container.RegisterType(typeof(IEventRepository), typeof(CachedEventRepository));

var eventRepo = container.Resolve<IEventRepository>();
eventRepo.GetAll();

助けてくれて本当にありがとうございます。

編集 1 以下は、私が実行できることを望んでいた Moq テストです。継承を使用して実行することはできず、DI が必要になるとは思いません。

var cacheProvider = new MemoryCaching();

var eventRepo = new Mock<IEventRepository>(MockBehavior.Strict);
eventRepo
    .Setup(x => x.GetAll())
    .Returns(() =>
    {
        return new Event[] { 
            new Event() { Id = 1}, 
            new Event() { Id = 2}
        };
    });

var cachedEventRepo = new CachedEventRepository(
    eventRepo.Object, 
    cacheProvider);

var data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Once());

// This set method should expire the cache so next time get all is requested it should
// load from the database again
cachedEventRepo.SomeSetMethod();

data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Exactly(2));
4

3 に答える 3

2

わかりました、このテーマについて考え、Unity について調査した後、私はこれを思いつきました。

public class EventRepository : IEventRepository
{
    private readonly IDbManager _dbManager;

    public EventRepository(IDbManager dbManager)
    {
        _dbManager = dbManager;
    }

    public virtual IEnumerable<Event> GetAll()
    {
        // Data access code
    }
}

public class CachedEventRepository : IEventRepository
{
    private readonly ICacheProvider _cacheProvider;
    private readonly IEventRepository _eventRepo;

    public ICacheProvider CacheProvider
    {
        get { return _cacheProvider; }
    }

    public CachedEventRepository(IEventRepository eventRepo, ICacheProvider cacheProvider)
    {
        if(eventRepo is CachedEventRepository)
            throw new ArgumentException("eventRepo cannot be of type CachedEventRepository", "eventRepo");

        _cacheProvider = cacheProvider;
        _eventRepo = eventRepo;
    }

    public IEnumerable<Event> GetAll()
    {
        // Caching logic for this method with a call to _eventRepo.GetAll() if required
    }
}

これには、次のユニティ登録が必要です。IEventRepository の解決リクエストは、CachedEventRepository を返します。キャッシングをすぐに削除したい場合は、その CachedEventRepository 登録を削除するだけで、EventRepository に戻ります。

IUnityContainer container = new UnityContainer();
container.RegisterType<IDbManager, SqlDbManager>();
container.RegisterType<ICacheProvider, MemoryCaching>();
container.RegisterType<IEventRepository, EventRepository>();
container.RegisterType<IEventRepository, CachedEventRepository>(
    new InjectionConstructor(
        new ResolvedParameter<EventRepository>(),
        new ResolvedParameter<ICacheProvider>())
    );

これにより、私が求めている正確なテストが可能になります。

簡単なデータ アクセス テスト... SQL は機能しますか

IUnityContainer container = new UnityContainer();
container.RegisterType<IDbManager, SqlDbManager>();
container.RegisterType<EventRepository>();

var repo = container.Resolve<EventRepository>();

var data = repo.GetAll();

Assert.IsTrue(data.Count() > 0);

簡単なキャッシュ テスト...キャッシュ システムは機能しますか

var cache = new MemoryCaching();

var getVal = cache.Get<Int32>(
    "TestKey",
    () => { return 2; },
    DateTime.UtcNow.AddMinutes(5));

Assert.AreEqual(2, getVal);

getVal = cache.Get<Int32>(
    "TestKey",
    () => { throw new Exception("This should not be called as the value should be cached"); },
    DateTime.UtcNow.AddMinutes(5));

Assert.AreEqual(2, getVal);

そして、2つの連携のテスト...個々のメソッドのキャッシュは期待どおりに機能しますか。キャッシュは期限切れになるはずですが、メソッド引数が正しく機能して新しいデータベース要求をトリガーしますか?

var cacheProvider = new MemoryCaching();

var eventRepo = new Mock<IEventRepository>(MockBehavior.Strict);
eventRepo
    .Setup(x => x.GetAll())
    .Returns(() =>
    {
        return new Event[] { 
            new Event() { Id = 1}, 
            new Event() { Id = 2}
        };
    });

var cachedEventRepo = new CachedEventRepository(
    eventRepo.Object,
    cacheProvider);


cachedEventRepo.CacheProvider.Clear();
var data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Once());


cachedEventRepo.SomeSetMethodWhichExpiresTheCache();
data = cachedEventRepo.GetAll();
data = cachedEventRepo.GetAll();
Assert.IsTrue(data.Count() > 0);
eventRepo.Verify(x => x.GetAll(), Times.Exactly(2));

これについてどう思いますか?良好な分離と良好なテスト容易性を提供すると思います。

于 2012-04-20T10:23:31.847 に答える
1

すべてのキャッシュ ロジックを 1 つのクラスにカプセル化してみませんか? したがって、次のようなものが得られます。

public interface ICacheManager {}

public class CacheManager : ICacheManager {}

したがって、単体テストをすべて記述して、キャッシュ ロジックが正常であることを確認できます。そしてCacheManagerTest授業になります!

次に、この方法でクラスを変更できます。

public class EventRepository : IEventRepository
{
private ICacheManager _cacheManager;
public EventRepository(ICacheManager cacheManager)
{
    _cacheManager = cacheManager;
}
    public IEnumerable<Event> GetAll()
    {
        // Data access code here
    }
}

EventRepositoryTestしたがって、クラスでキャッシュロジックをテストする必要はありません。すでにテストされているためです。

IoC コンテナーをセットアップして、キャッシュ ポリシーのパラメーターを使用して ICacheManager のインスタンスを返すことができます。

更新 OK、最後の試行:

public interface IEventRepo
{
    IEnumerable<Event> GetAll();
}

public interface ICacheProvider
{
    bool IsDataCached();
    IEnumerable<Event> GetFromCache();
}

public class CacheProvider : ICacheProvider
{

    public bool IsDataCached()
    {
        //do smth
    }

    public IEnumerable<Event> GetFromCache()
    {
        //get smth
    }
}


public class EventRepo : IEventRepo
{
    private ICacheProvider _cacheProvider;

    public EventRepo(ICacheProvider cacheProvider)
    {
     _cacheProvider = cacheProvider
    }

    public IEnumerable<Event> GetAll()
    {
        if (_cacheProvider.IsDataCached())
        {
            return _cacheProvider.GetFromCache();
        }
        else
        {
            //get from repo, save data in cache etc
        }
    }
}

[TestClass]
public class EventRepoTest
{
    [TestMethod]
    public void GetsDataFromCacheIfDataIsCachedTest()
    {
        var cacheProvider = new Mock<ICacheProvider>(MockBehavior.Strict);
        cacheProvider
            .Setup(x => x.IsDataCached())
            .Returns(() =>
            {
                return true;
            });
        cacheProvider
            .Setup(x => x.GetFromCache())
            .Returns(
            () => {
            return new Event[] { 
                new Event() { Id = 1}, 
                new Event() { Id = 2}
                };
            }
            );
        var eventRepo = new EventRepo(cacheProvider.Object);

        var data = eventRepo.GetAll();
        cacheProvider.Verify(x => x.GetFromCache(), Times.Once());
    }

    [TestMethod]
    public void GetsDataFromDataBaseIfNotCachedTest()
    {
        var cacheProvider = new Mock<ICacheProvider>(MockBehavior.Strict);
        cacheProvider
            .Setup(x => x.IsDataCached())
            .Returns(() =>
            {
                return false;
            });
        cacheProvider
            .Setup(x => x.GetFromCache())
            .Returns(
            () =>
            {
                return new Event[] { 
                new Event() { Id = 1}, 
                new Event() { Id = 2}
                };
            }
            );
        var eventRepo = new EventRepo(cacheProvider.Object);

        var data = eventRepo.GetAll();
        cacheProvider.Verify(x => x.GetFromCache(), Times.Never());
    }
}

WinPhone 用の Moq がないため、Moq の構文についてはわかりませんが、問題ないと思います。

于 2012-04-19T16:03:30.130 に答える
1

あなたは CachedEventRepository で正しい軌道に乗っていると思いますが、EventRepository の GetAll メソッドを仮想化し、CachedEventRepository サブクラス EventRepostory を作成します。次に、サブクラスは GetAll をオーバーライドし、キャッシュをチェックし、何も見つからない場合は base.GetAll を呼び出します。その後、結果をキャッシュしてリストを返すことができます。

このように、キャッシュ ロジックはデータ アクセス ロジックから分離され、サブクラスはキャッシュ動作をリポジトリに追加します。

次に、キャッシュされたリポジトリが必要かどうかを構成ファイルから選択し、Unity コンテナーを適切に構成できます。

また、キャッシュ サービス用のインターフェイスも用意できるため、CachedEventRepository の単体テスト時にそれをモックできます。

于 2012-04-19T16:03:56.137 に答える