2013-08-04 154 views
2

我有一个服务层,它有一系列的方法。这些方法已经实现缓存,如下所示:如何对使用缓存的服务进行单元测试?

string key = "GetCategories"; 
if (CacheHandler.IsCachingEnabled() && !CacheHandler.ContainsKey(key)) 
{ 
    var categories = RequestHelper.MakeRequest("get_category_index")["categories"]; 
    var converted = categories.ToObject<List<Category>>(); 
    CacheHandler.InsertToCache(key,converted); 
    return converted; 
} 
return CacheHandler.GetCache(key) as List<Category>; 

现在,问题是我也要打个单元测试,如下所示:

[TestMethod] 
public void GetCategories() 
{ 
    IContentService contentService = new ContentService(); 
    var resp = contentService.GetCategories(); 
    Assert.IsNotNull(resp,"Should not be null"); 
} 

问题是,该HttpContext.Current在我的CacheHandler在单元测试期间显然是空的。

解决此问题的最简单方法是什么?

回答

3

这个尖叫dependency injection。我认为主要的问题是,你访问CacheHandler静态,所以在一个单元测试,您可以:
一)不能没有“测试”的CacheHandler以及
二)不能提供任何其他CacheHandler到服务测试服务,例如mocked一个

如果你的情况是可能的,我想无论是重构或者至少包裹CacheHandler,使该服务访问它的一个实例。在单元测试中,您可以使用“假”CacheHandler提供服务,该服务不会访问HttpContext,并且可以让您对测试本身进行非常好的控制(例如,您可以测试缓存项目时发生的情况与当它不是两个完全独立的单元测试)

对于嘲讽的一部分,我想这是最简单的创建一个接口,然后使用专门用于测试一些automocking /代理生成框架,例如Rhino Mocks(但还有更多,只是发生了,我正在使用这一个,我很高兴:))。另一种方法(对于初学者更容易,但在实际开发中更麻烦)将仅仅是设计CacheHandler(或其包装),以便您可以继承它并自行覆盖行为。

最后对于注入本身,我发现了一个方便的“模式”,它利用了C#默认方法参数和标准构造函数注入。该服务的构造看起来像:

public ContentService(ICacheHandler cacheHandler = null) 
{ 
    // Suppose I have a field of type ICacheHandler to store the handler 
    _cacheHandler = cacheHandler ?? new CacheHandler(...); 
} 

所以在应用程序本身,我可以调用不带参数的构造函数(或让框架构建的服务,如果它的ASP.NET处理程序,WCF服务或一些其他类型的类)和单元测试中,我可以提供实现上述接口的任何内容。

在犀牛嘲笑的情况下,它可以是这样的:

var mockCacheHandler = MockRepository.GenerateMock<ICacheHandler>(); 
// Here I can mock/stub methods and properties, set expectations etc... 
var sut = new ContentService(mockCacheHandler); 
+1

它的工作!完善!这真的帮助我马上:)犀牛嘲笑也非常好。谢谢 –

1

作为最佳实践(因为我没有做了很多以前的单元测试请尽可能具体),你只需要在测试期间,测试一件事,你所描述的有什么多个步骤。因此,最好构建你的“RequestHelper.MakeRequest”和其他例程的测试,以便它们与缓存场景分开运行测试。单独测试它们会让你知道它们是否是这些例程或缓存中的问题。您可以稍后将其整合,以便将其作为一个组进行测试。

要单独测试缓存,可以创建一个模拟对象,以使用您需要的属性创建HttpContext。下面是一些以前的答案,应该帮助你把这个在一起:

How do I mock the HttpContext in ASP.NET MVC using Moq?

Mock HttpContext.Current in Test Init Method

2

独立缓存到自己的类,就像是一个代理。

public interface IContentService 
{ 
    Categories GetCategories(); 
} 

public class CachingContentSerice : IContentService 
{ 
    private readonly IContentService _inner; 

    public CachingContentSerice(IContentService _inner) 
    { 
     _inner = inner; 
    } 

    public Categories GetCategories() 
    { 
     string key = "GetCategories"; 
     if (!CacheHandler.ContainsKey(key)) 
     { 
      Catogories categories = _inner.GetCategories(); 
      CacheHandler.InsertToCache(key, categories); 
     } 
     return CacheHandler.GetCache(key); 
    } 
} 

public class ContentSerice : IContentService 
{ 
    public Categories GetCategories() 
    { 
     return RequestHelper.MakeRequest("get_category_index")["categories"]; 
    } 
} 

要启用缓存,与缓存装点真正ContentService

var service = new CachingContentService(new ContentService()); 

为了测试缓存,与测试双象构造器参数创建CachingContentService。使用test double验证缓存:调用一次,它应该调用后面的服务。调用它两次,它不应该调用后面的服务。

3
在洪扎Brestan的答案建议

依赖注入无疑是一个有效的解决方案,也许最好的解决方案 - 特别是如果你可能想使用在未来的ASP.NET缓存以外的东西。

但是我应该指出,您可以使用ASP.NET缓存而不需要HttpContext。您可以使用静态属性HttpRuntime.Cache,而不是将其引用为HttpContext.Current.Cache

这将使您能够在HTTP请求的上下文之外使用缓存,例如在单元测试或后台工作线程中。事实上,我通常会建议在业务层中使用HttpRuntime.Cache进行数据缓存,以避免依赖HttpContext的存在。

相关问题