Подтвердить что ты не робот

Unit test HttpContext.Current.Cache или другие серверные методы в С#?

При создании unit test для класса, который использует HttpContext.Current.Cache class, я получаю сообщение об ошибке при использовании NUnit. Функциональность базовая - проверьте, находится ли элемент в кеше, а если нет, создайте его и поместите в:

if (HttpContext.Current.Cache["Some_Key"] == null) {
    myObject = new Object();
    HttpContext.Current.Cache.Insert("Some_Key", myObject);
}
else {
    myObject = HttpContext.Current.Cache.Get("Some_Key");
}

При вызове этого из unit test он не работает с NullReferenceException при встрече с первой строкой Cache. В Java я бы использовал Cactus для проверки кода на стороне сервера. Есть ли аналогичный инструмент, который я могу использовать для кода С#? Этот вопрос SO упоминает макетные рамки - это единственный способ проверить эти методы? Есть ли аналогичный инструмент для запуска тестов для С#?

Кроме того, я не проверяю, является ли Cache нулевым, поскольку я не хочу писать код специально для unit test и предполагаю, что он всегда будет действителен при работе на сервере. Является ли это допустимым или следует добавить к кешу нулевые проверки?

4b9b3361

Ответ 1

Способ сделать это - избежать прямого использования HttpContext или других подобных классов и заменить их на mocks. В конце концов, вы не пытаетесь проверить правильность работы HttpContext (это задание на Microsoft), вы просто пытаетесь проверить, вызваны ли методы, когда они должны быть.

Шаги (В случае, если вы просто хотите знать технику, не выкапывая множество блогов):

  • Создайте интерфейс, который описывает методы, которые вы хотите использовать в кешировании (возможно, такие вещи, как GetItem, SetItem, ExpireItem). Назовите это ICache или как вам нравится

  • Создайте класс, который реализует этот интерфейс, и передает методы в реальный HttpContext

  • Создайте класс, который реализует один и тот же интерфейс, и просто действует как макетный кеш. Он может использовать словарь или что-то, если вам нужно сохранять объекты

  • Измените исходный код, чтобы он вообще не использовал HttpContext и вместо этого использовался только ICache. Затем код должен получить экземпляр ICache - вы можете либо передать экземпляр в свой конструктор классов (это все, что на самом деле представляет собой зависимость), либо вставить его в некоторую глобальную переменную.

  • В вашем рабочем приложении установите ICache в качестве своего реального кэша под HttpContext-Backed-Cache, а в ваших модульных тестах установите ICache в макет кеша.

  • Profit!

Ответ 2

Я согласен с другими, что использование интерфейса будет лучшим вариантом, но иногда просто невозможно изменить существующую систему. Вот какой код, который я только что объединил с одним из моих проектов, который должен дать вам результаты, которые вы ищете. Его самая дальняя вещь от красивого или отличного решения, но если вы действительно не можете изменить свой код, тогда он должен выполнить свою работу.

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

[TestFixture]
public class HttpContextCreation
{
    [Test]
    public void TestCache()
    {
        var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
        var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
        SetPrivateInstanceFieldValue(result, "m_HostContext", context);

        Assert.That(HttpContext.Current.Cache["val"], Is.Null);

        HttpContext.Current.Cache["val"] = "testValue";
        Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue"));
    }

    private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
    {
        var sb = new StringBuilder();
        var sw = new StringWriter(sb);
        var hres = new HttpResponse(sw);
        var hreq = new HttpRequest(fileName, url, queryString);
        var httpc = new HttpContext(hreq, hres);
        return httpc;
    }

    private static object RunInstanceMethod(object source, string method, object[] objParams)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        var type = source.GetType();
        var m = type.GetMethod(method, flags);
        if (m == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
        }

        var objRet = m.Invoke(source, objParams);
        return objRet;
    }

    public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
    {
        var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null)
        {
            throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
        }

        field.SetValue(source, value);
    }
}

Ответ 3

HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));

Ответ 4

Если вы используете .NET 3.5, вы можете использовать System.Web.Abstractions в своем приложении.

Justin Etheredge имеет отличный post о том, как издеваться над HttpContext (который содержит класс кеша).

Из примера Justin я передаю HttpContextBase своим контроллерам, используя HttpContextFactory.GetHttpContext. Когда они насмехаются над ними, я просто создаю Mock для вызова кэш-объекту.

Ответ 5

Существует более новый подход, помогающий конкретно решать проблемы с кешем в модульных тестах.

Я бы рекомендовал использовать новый подход MemoryCache.Default Microsoft. Вам понадобится использовать .NET Framework 4.0 или новее и включить ссылку на System.Runtime.Caching.

См. статью здесь → http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default работает как для веб-приложений, так и для не-веб-приложений. Итак, идея заключается в том, что вы обновляете свой webapp, чтобы удалить ссылки на HttpContext.Current.Cache и заменить их ссылками на MemoryCache.Default. Позже, когда вы запустите решение Unit Test этих же методов, объект кэша все еще доступен и не будет пустым. (Потому что он не зависит от HttpContext.)

Таким образом, вам даже необязательно нужно издеваться над компонентом кэша.

Ответ 6

Общий консенсус, по-видимому, заключается в том, что вождение всего HttpContext, связанного с unit test, является полным кошмаром, и его следует избегать, если это возможно.

Я думаю, ты на правильном пути насмехается. Мне нравится RhinoMocks (http://ayende.com/projects/rhino-mocks.aspx).

Я тоже кое-что прочитал о MoQ (http://code.google.com/p/moq), хотя я еще не пробовал его.

Если вы действительно хотите писать единичные тестируемые веб-интерфейсы на С#, то, как люди, кажется, заголовок, должен использовать структуру MVC (http://www.asp.net/mvc), а не WebForms...

Ответ 7

Вы можете использовать класс HttpContextBase в System.Web.Abstractions.dll. Это новая dll в .NET 3.5.

Вы можете найти пример использования в приведенной ниже ссылке.

http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/

Ответ 8

Это может быть на вашей улице... Фил Хаак демонстрирует, с помощью ночных издевательств Rhino, как издеваться над httpcontext в asp mvc, но я бы предположил, что он может применяться к веб-формам.

Clicky!!

Надеюсь, что это поможет.

Ответ 9

если вы не заботитесь о кеше кеша, который вы можете сделать ниже:

[TestInitialize]
    public void TestInit()
    {
      HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
    }

Также вы можете moq как ниже

var controllerContext = new Mock<ControllerContext>();
      controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
      controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));

Ответ 10

Все эти вопросы программирования задают модель программирования на основе интерфейса, в которой вы дважды реализуете интерфейс. Один для реального кода и один для макета.

Затем возникает следующая проблема. Для этого можно использовать несколько шаблонов проектирования. См., Например, знаменитые шаблоны GangOfFour Creation (GOF) или шаблоны Injection Dependency.

ASP.Net MVC фактически использует этот интерфейсный подход и, следовательно, гораздо более подходит для модульного тестирования.

Ответ 11

Как все говорят здесь, есть проблема с HTTPContext, в настоящее время Typemock является единственной структурой, которая может подделать ее напрямую без каких-либо оберток или абстракции.

Ответ 12

Объект кэша сложно высмеять, поскольку он является запечатанной областью среды .NET. Обычно я обхожу это, создавая класс оболочки кэша, который принимает объект менеджера кэша. Для тестирования я использую диспетчер кэширования макета; для производства я использую диспетчер кэша, который фактически обращается к HttpRuntime.Cache.

В принципе, я сам отбрасываю кеш.

Ответ 13

Пример для тех, кто использует MVC 3 и MOQ:

Мой метод контроллера имеет следующую строку:

model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList] 
as Dictionary<int, string>);

Таким образом, любой unit test завершится неудачно, поскольку я не настрою HttpContext.Cache.

В моем unit test я соглашусь следующим образом:

 HttpRuntime.Cache[Constants.C_CustomerTitleList] = new Dictionary<int, string>();

 var mockRequest = new Mock<HttpRequestBase>();
 mockRequest.SetupGet(m => m.Url).Returns(new Uri("http://localhost"));

 var context = new Mock<HttpContextBase>(MockBehavior.Strict);
 context.SetupGet(x => x.Request).Returns(mockRequest.Object);
 context.SetupGet(x => x.Cache).Returns(HttpRuntime.Cache);

 var controllerContext = new Mock<ControllerContext>();
 controllerContext.SetupGet(x => x.HttpContext).Returns(context.Object);

 customerController.ControllerContext = controllerContext.Object;

Ответ 14

Мог бы попробовать...

 Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake();
 var fakeSession = HttpContext.Current.Session;
 Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");