Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/oracle/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何对使用缓存的服务进行单元测试?_C#_Asp.net Mvc_Unit Testing_Caching - Fatal编程技术网

C# 如何对使用缓存的服务进行单元测试?

C# 如何对使用缓存的服务进行单元测试?,c#,asp.net-mvc,unit-testing,caching,C#,Asp.net Mvc,Unit Testing,Caching,我有一个服务层,它有一系列的方法。这些方法实现了缓存,如下所示: string key = "GetCategories"; if (CacheHandler.IsCachingEnabled() && !CacheHandler.ContainsKey(key)) { var categories = RequestHelper.MakeRequest("get_category_index")["categories"]; var converted = ca

我有一个服务层,它有一系列的方法。这些方法实现了缓存,如下所示:

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");
}
问题是,在单元测试期间,我的
CacheHandler
中的
HttpContext.Current
为空(显然)

解决这个问题最简单的方法是什么


(请尽可能具体,因为我以前没有做过很多单元测试)

作为一种最佳实践,您在测试期间只想测试一件事情,并且您所描述的有多个步骤。因此,最好构造“RequestHelper.MakeRequest”和其他例程的测试,以便它们与缓存场景分开运行测试。单独测试它们会让您知道它们是否存在这些例程或缓存中的问题。您可以在以后集成它们以将它们作为一个组进行测试

要单独测试缓存,您可以创建一个模拟对象来创建具有所需属性的HttpContext。以下是一些以前的答案,这些答案应该可以帮助您将这些问题综合起来:


将缓存分离到自己的类中,就像代理一样工作

public interface IContentService
{
    Categories GetCategories();
}

public class CachingContentService : 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());
要测试缓存,请使用test double作为构造函数参数创建
CachingContentService
。使用双重测试来验证缓存:调用它一次,它应该调用后面的服务。呼叫它两次,它就不应该呼叫后面的服务。

这让人尖叫。我看到的主要问题是静态访问
CacheHandler
,因此在单元测试中,您:
a) 如果不同时“测试”缓存处理程序,则无法测试服务
b) 无法向服务提供任何其他
CacheHandler
,例如一个

如果在您的情况下可能的话,我会重构或至少包装
CacheHandler
,以便服务访问它的一个实例。在单元测试中,您可以为服务提供一个“假”
CacheHandler
,它不会访问HttpContext,还可以让您对测试本身有一个非常好的控制(例如,您可以测试缓存项与不在两个绝对独立的单元测试中时会发生什么)

对于模拟部分,我认为最简单的方法是创建一个接口,然后使用一些为测试而设计的自动模拟/代理生成框架,例如(但是还有很多,我恰好使用了这个,并且非常满意:)。另一种方法(对于初学者来说更容易,但在实际开发中更麻烦)是简单地设计
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);
var mockCacheHandler=MockRepository.GenerateMock();
//在这里,我可以模拟/存根方法和属性,设置期望值等。。。
var sut=newcontentservice(mockCacheHandler);

Honza Brestan的答案中推荐的依赖注入肯定是一个有效的解决方案,而且可能是最好的解决方案-特别是如果您将来可能想使用ASP.NET缓存以外的其他东西

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


这将使您能够在HTTP请求的上下文之外使用缓存,例如在单元测试或后台工作线程中。事实上,我通常建议在业务层中使用
HttpRuntime.Cache
进行数据缓存,以避免依赖于
HttpContext的存在,因为它是有效的!完美的这真的帮了我的忙:)犀牛嘲弄也非常好。谢谢