2013-08-04 2 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, а также может дать вам очень тонкий контроль над самим тестом (например, вы можете проверить, что происходит, когда элемент кэшируется vs.когда он не в двух абсолютно независимых единичных испытаний)

Для насмешливым, то я полагаю, что это проще всего создать интерфейс, а затем использовать некоторые рамки 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 или какой-либо другой вид класса) и в модульных тестах я могу предоставить все, что реализует упомянутый интерфейс.

В случае Rhino Mocks, он может выглядеть следующим образом:

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

Это сработало! Отлично! Это действительно помогло мне сразу :) Это Rhino Mocks также очень хорошо. благодаря –

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 с двойным тестом в качестве параметра застройщика. Используйте двойной тест для проверки кеша: вызовите его один раз, и он должен вызвать службу позади. Вызовите его дважды, и он не должен вызывать услугу.

3

Dependency Injection, как рекомендовано в ответе Honza Brestan является, безусловно, правильным решением, и, возможно, самым лучшим решением - особенно, если вы можете использовать что-то, кроме кэша ASP.NET в будущем.

Однако я должен указать, что вы можете использовать кэш ASP.NET без необходимости использования HttpContext. Вместо того, чтобы ссылаться на него как HttpContext.Current.Cache, вы можете использовать статическое свойство HttpRuntime.Cache.

Это позволит вам использовать кеш вне контекста HTTP-запроса, например, в модульном тесте или в потоке рабочего фона. Фактически я обычно рекомендую использовать HttpRuntime.Cache для кэширования данных в бизнес-уровне, чтобы избежать зависимости от существования HttpContext.