6

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

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.Currentmy 内部CacheHandler为空(显然)。

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

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

4

4 回答 4

8

这尖叫着依赖注入。我看到的主要问题是您CacheHandler静态访问,因此在单元测试中,您: a)如果没有“测试”也
无法测试服务 b)无法为服务提供任何其他服务,例如模拟的服务CacheHandler
CacheHandler

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

对于模拟部分,我想最简单的方法是创建一个界面,然后使用一些为测试而设计的自动模拟/代理生成框架,例如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 服务或其他类型的类),并且在单元测试中,我可以提供实现上述内容的任何内容界面。

在 Rhino Mocks 的情况下,它可能如下所示:

var mockCacheHandler = MockRepository.GenerateMock<ICacheHandler>();
// Here I can mock/stub methods and properties, set expectations etc...
var sut = new ContentService(mockCacheHandler);
于 2013-08-04T12:59:35.243 回答
4

Honza Brestan 的回答中推荐的依赖注入当然是一个有效的解决方案,也许是最好的解决方案 - 特别是如果您将来可能想使用 ASP.NET 缓存以外的东西。

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

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

于 2013-08-04T14:33:12.343 回答
3

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

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

要测试缓存,请CachingContentService使用 test double 作为构造函数参数来创建。使用测试替身来验证缓存:调用一次,它应该调用后面的服务。调用它两次,它不应该调用后面的服务。

于 2013-08-04T12:59:12.777 回答
1

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

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

如何使用 Moq 在 ASP.NET MVC 中模拟 HttpContext?

在测试初始化​​方法中模拟 HttpContext.Current

于 2013-08-04T12:57:51.187 回答