4

我现在有一些空闲时间,所以我试图让我的头绕过DI和IoC容器。我从没有理由的情况下选择了团结,除了我可以告诉我的主要框架之间没有太大的区别之外,我应该过于在意开始。随着事情变得越来越复杂,我意识到我可能需要改变,但现在我希望它会做。DI,IoC,Unity和Moq的类型注册和实施令人困惑

因此,我正在使用一个相对简单的数据访问方案,并已实现了以下接口和数据访问类。

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 以下是起订量的测试,我希望我能完成,我不认为将有可能使用继承和需要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)); 
+0

您不应该在eventRepo单元测试中测试缓存日志。它揭示了SOLID范式的单一职责理念。 – 2012-04-19 20:57:19

+0

我正在分别测试缓存和eventRepo。我试图测试的是将两者结合在一起,例如确保设置的方法过期相应的缓存。 – Hawxby 2012-04-20 08:18:37

+0

是的,是的,我明白了。我现在没有更多的想法。 – 2012-04-20 08:33:09

回答

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); 

而且两者的测试合作...个别方法上的缓存是否按预期工作。缓存是否应该过期,方法参数是否正常工作以触发新的数据库请求等。

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)); 

您对此有何看法?我认为它提供了良好的分离和良好的可测试性。

+0

是的,这个比你的第一个变体好得多。 – 2012-04-20 10:51:32

+0

非常好,非常感谢您对此的帮助! – Hawxby 2012-04-21 10:42:24

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类来测试缓存逻辑,COSE它alredy测试。

比你可以设置你的IoC容器返回一个ICacheManager的实例与缓存策略的一些参数。

更新 好吧,最后一次尝试:

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,但我认为这不是一个问题。

+0

我已经创建了一个ICacheProvider和内存缓存实现,以实现您的建议。然而,我不愿意将它注入到EventRepository中,因为这会阻止测试EventRepository从缓存逻辑中分离出来。 – Hawxby 2012-04-19 17:01:04

+0

在单元测试中,您可以注入一个简单的缓存提供程序,它只是返回模拟数据,甚至什么也不做。所以你可以避免对缓存逻辑进行双重测试。 – 2012-04-19 20:52:06

+0

这将双重测试缓存逻辑到一定程度。我没有直接测试缓存,也没有测试数据eventRepository,我正在测试两个连接在一起的实现来创建cachedEventRepository。因此,检查适当的方法会自动使缓存过期,导致缓存更新等。 – Hawxby 2012-04-20 06:49:29

1

我认为你在CachedEventRepository的正确轨道上,但我会让EventRepository的GetAll方法变为虚拟,并且拥有CachedEventRepository子类EventRepoistory。然后子类可以重写GetAll,检查缓存,并且如果它找不到任何调用base.GetAll。然后它可以缓存结果并返回列表。

通过这种方式,缓存逻辑与数据访问逻辑分开,并且子类将缓存行为添加到存储库。

然后,您可以选择是否需要缓存存储库,例如从配置文件中进行配置并适当地配置Unity容器。另外,你也可以为你的缓存服务提供一个接口,这样你可以在单元测试你的CachedEventRepository时将它嘲笑掉。

+0

我这样做的唯一问题是我无法传递一个依赖的EventRepository,所以我可以使用Moq,并确保在缓存清理后的特定时间段内调用x方法的次数为x并且不是 – Hawxby 2012-04-19 17:17:33

+0

@Hawxby我是不知道我跟着。 Cached子类采用“ICachingService”接口。它取决于您实现该接口以实际缓存正确的时间段。缓存存储库应该完全不知道缓存是如何实际完成的;它应该总是试图从缓存中获取,如果它没有得到任何数据库应该命中。否则,你的缓存仓库正在做两件事情;管理缓存以及利用缓存,这违反了分离问题。 – Andy 2012-04-19 18:19:58

+0

看看我对这个问题的编辑,我认为这证明了我在测试之后。 – Hawxby 2012-04-19 20:40:50