2009-01-28 19 views
33

Al crear una prueba unitaria para una clase que utiliza el HttpContext.Current.Cache class, me sale un error cuando se utiliza NUnit. La funcionalidad es básico - comprobar si un artículo está en la caché, y si no, lo crea y lo puso en:Prueba unitaria HttpContext.Current.Cache u otros métodos del lado del servidor en C#?

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"); 
} 

Cuando se llama a esto desde una prueba de unidad, se produce un error con al NullReferenceException cuando se enfrentan a la primera Cache línea. En Java, usaría Cactus para probar el código del lado del servidor. ¿Hay alguna herramienta similar que pueda usar para el código C#? This SO question menciona marcos falsos: ¿es esta la única forma en que puedo probar estos métodos? ¿Hay alguna herramienta similar para ejecutar pruebas de C#?

Además, no compruebo si el Cache es nulo, ya que no quiero escribir el código específicamente para la prueba unitaria y supongo que siempre será válido cuando se ejecute en un servidor. ¿Es esto válido, o debería agregar controles nulos alrededor de la caché?

Respuesta

42

La manera de hacer esto es evitar el uso directo de la HttpContext u otras clases similares, y sustituirlos con burla. Después de todo, no está tratando de probar que el HttpContext funciona correctamente (ese es el trabajo de Microsoft), solo está tratando de probar que los métodos se llamaron cuando deberían.

Pasos (En caso de que sólo quieren saber la técnica sin excavar a través de un montón de blogs):

  1. Crear una interfaz que describe los métodos que desea utilizar en el almacenamiento en caché (probablemente cosas como GetItem, SetItem, ExpireItem). Llámalo iCache o lo que le gusta

  2. Crear una clase que implementa la interfaz, y pasa métodos a través de la HttpContext verdadera

  3. Crear una clase que implementa la misma interfaz, y simplemente actúa como una caché de simulacro. Puede usar un diccionario o algo así si le importa guardar objetos

  4. Cambie el código original para que no utilice el HttpContext y, en su lugar, solo use un ICache.El código tendrá que obtener una instancia del ICache: puede pasar una instancia en el constructor de las clases (esto es todo lo que realmente es la inyección de dependencia) o pegarla en alguna variable global.

  5. En su aplicación para la producción, establezca la iCache ser su verdadero respaldo HttpContext-Cache-y en sus pruebas de unidad, establezca la iCache ser el caché simulado.

  6. ¡Ganar!

2

consenso general parece ser que todo lo relacionado con la conducción HttpContext desde el interior de una prueba unitaria es una pesadilla, y se debe evitar si es posible.

Creo que estás en el camino correcto con respecto a burla. Me gusta RhinoMocks (http://ayende.com/projects/rhino-mocks.aspx).

He leído algunas cosas buenas de MoQ también (http://code.google.com/p/moq), aunque no lo he probado todavía.

Si realmente desea escribir unidad comprobable web interfaces de usuario en C#, la forma en que la gente parece estar dirigiéndose es usar el framework MVC (http://www.asp.net/mvc) en lugar de formularios Web ...

6

Si está utilizando .NET 3.5, puede usar System.Web.Abstractions en su aplicación.

Justin Etheredge tiene un gran post sobre cómo burlarse de HttpContext (que contiene la clase de caché).

Según el ejemplo de Justin, paso una HttpContextBase a mis controladores utilizando HttpContextFactory.GetHttpContext. Al burlarse de ellos, simplemente construyo un simulacro para hacer llamadas al objeto de caché.

+6

Puede burlarse casi cualquier cosa con HttpContextBase, pero la propiedad Cache no está entre ellos. HttpContextBase.Cache es del tipo System.Web.Caching.Cache que está sellado, inutilizable en pruebas unitarias y no se puede burlar ... – chris166

0

Todas estas preguntas programación piden un modelo de programación basado en la interfaz, en el que se implementa la interfaz de dos veces. Uno para el código real y otro para la maqueta.

La instanciación es entonces el siguiente problema. Hay varios patrones de diseño que se pueden usar para eso. Vea, por ejemplo, los famosos patrones Creational GangOfFour (GOF) o los patrones de Dependency Injection.

ASP.Net MVC está de hecho utilizando este enfoque basado en interfaz y, por lo tanto, es mucho más adecuado para pruebas unitarias.

28

Estoy de acuerdo con los demás en que usar una interfaz sería la mejor opción, pero a veces simplemente no es posible cambiar un sistema existente. Aquí hay un código que simplemente mezclé de uno de mis proyectos que debería darte los resultados que estás buscando. Es lo más alejado de una solución bonita o excelente, pero si realmente no puede cambiar su código, entonces debería hacer el trabajo.

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); 
    } 
} 
0

Como todo el mundo dice aquí, hay un problema con HttpContext, actualmente Typemock es el único marco que se puede fingir directamente sin ningún tipo de envoltorios o abstracciones.

1

Esto puede pasar por tu calle ... Phil Haack muestra, con la ayuda de Rhino, cómo se burla de httpcontext en asp mvc, pero imagino que se puede aplicar a formularios web.

Clicky!!

Espero que esto ayude.

0

El objeto de caché es difícil de simular porque es un área sellada del framework .NET. Normalmente soluciono esto construyendo una clase de contenedor de caché que acepta un objeto de administrador de caché. Para las pruebas, utilizo un administrador de caché falso; para producción utilizo un administrador de caché que realmente accede a HttpRuntime.Cache.

Básicamente, extraigo el caché.

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

Un ejemplo para aquellos que utilizan MVC 3 y MOQ:

Mi método controlador tiene la línea siguiente:

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

Como tal, cualquier prueba de la unidad fallará, ya que no estoy configurando HttpContext.Cache.

En mi prueba de unidad, yo arregle como sigue:

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; 
5

No es un nuevo enfoque para ayudar a tratar específicamente de caché en las pruebas unitarias.

Recomendaría usar el nuevo de Microsoft MemoryCache.Default enfoque. Tendrá que utilizar .NET Framework 4.0 o posterior e incluir una referencia a System.Runtime.Caching.

Ver artículo aquí ->http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default funciona tanto para aplicaciones web y no web. Entonces, la idea es actualizar su aplicación web para eliminar las referencias a HttpContext.Current.Cache y reemplazarlas por referencias a MemoryCache.Default. Más tarde, cuando corras decide probar la Unidad estos mismos métodos, el objeto de caché todavía está disponible y no será nulo. (Debido a que no depende de un HttpContext.)

De esta forma ni siquiera es necesario que se burle del componente de caché.

1

si no se preocupan por las pruebas de memoria caché que puede hacer a continuación:

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

también le puede moq, como a continuación

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")); 
0

podría intentar ...

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