unit testingHttpContext.Current.Cache或C#中的其他服务器端方法?

为使用HttpContext.Current.Cache类的类创buildunit testing时,使用NUnit时出现错误。 该function是基本的 – 检查一个项目是否在caching中,如果没有,创build它并把它放在:

 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 testing中调用这个NullReferenceException时,遇到第一个Cache行时,它在NullReferenceException时失败。 在Java中,我将使用Cactus来testing服务器端代码。 有没有类似的工具,我可以使用C#代码? 这个SO问题提到模拟框架 – 这是我可以testing这些方法的唯一方法吗? 有没有类似的工具来运行testing的C#?

此外,我不检查Cache是否为空,因为我不想专门为unit testing编写代码,并假定它在服务器上运行时始终有效。 这是否有效,还是应该在caching中添加空检查?

这样做的方法是避免直接使用HttpContext或其他类似的类,并用mockreplace它们。 毕竟,你并没有试图testingHttpContext是否正常工作(这是微软的工作),你只是试图testing,当他们应该有调用的方法。

步骤(如果你只是想知道这个技术,而不需要挖掘大量的博客):

  1. 创build一个接口,描述你想要在你的caching中使用的方法(可能像GetItem,SetItem,ExpireItem)。 叫它ICache或任何你喜欢的

  2. 创build一个实现该接口的类,并将方法传递给实际的HttpContext

  3. 创build一个实现相同接口的类,就像一个模拟caching。 如果您关心保存对象,它可以使用字典或其他东西

  4. 改变你的原始代码,使它根本不使用HttpContext,而是只使用一个ICache。 代码将需要得到一个ICache的实例 – 你可以在你的类构造函数中传递一个实例(这就是所有的dependency injection),或者把它放在一些全局variables中。

  5. 在生产应用程序中,将ICache设置为您真正的HttpContext-Backed-Cache,并在您的unit testing中将ICache设置为模拟caching。

  6. 利润!

我同意其他人的观点,认为使用界面是最好的select,但有时候改变现有的系统是不可行的。 这里有一些代码,我只是从我的一个项目中把它们混合在一起,这些代码会给你你想要的结果。 这是最美妙的解决scheme,但如果你真的不能改变你的代码,那么它应该完成工作。

 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); } } 
 HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null)); 

如果您使用.NET 3.5,则可以在应用程序中使用System.Web.Abstractions。

Justin Etheredge有一篇关于如何模拟HttpContext(包含caching类)的好文章。

从Justin的例子中,我使用HttpContextFactory.GetHttpContext将HttpContextBase传递给我的控制器。 当嘲笑他们,我只是build立一个模拟来调用caching对象。

在unit testing中有一个更新的方法来帮助处理Cache。

我会build议使用微软新的MemoryCache.Default方法。 您将需要使用.NET Framework 4.0或更高版本,并包含对System.Runtime.Caching的引用。

在这里看到文章 – > http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default适用于Web和非Web应用程序。 所以你的想法是更新你的web应用程序,以删除对HttpContext.Current.Cache的引用,并将其replace为对MemoryCache.Default的引用。 后来,当你运行决定unit testing这些相同的方法时,caching对象仍然可用,不会为空。 (因为它不依赖于HttpContext。)

这样你甚至不一定需要模拟caching组件。

普遍的共识似乎是,在unit testing中驱动任何与HttpContext相关的东西是一个完全的噩梦,如果可能的话应该避免。

我认为你在嘲笑的道路上。 我喜欢RhinoMocks( http://ayende.com/projects/rhino-mocks.aspx )。

我也读过MoQ的一些好东西( http://code.google.com/p/moq ),虽然我还没有尝试过。

如果你真的想在C#中编写单元可testing的Web UI,那么人们似乎要使用MVC框架( http://www.asp.net/mvc )而不是WebForms。

您可以在System.Web.Abstractions.dll中使用HttpContextBase类。 这是.NET 3.5中的一个新的DLL。

你可以在下面的链接中find一个例子。

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

这可能是你的街道…菲尔·哈克炫耀,在犀牛嘲笑的帮助下,如何在asp mvc中模拟httpcontext,但我想可以应用到webforms。

Clicky!

希望这可以帮助。

如果你不关心testingcaching,你可以在下面做:

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

所有这些编程问题都要求提供一个基于接口的编程模型,其中您实现了两次接口。 一个用于真正的代码,一个用于模型。

实例化是下一个问题。 有几种可用于此的devise模式。 例如看到着名的GangOfFour Creational模式( GOF )或dependency injection模式。

ASP.Net MVC实际上是使用这种基于接口的方法,因此更适合unit testing。

正如大家所说,HTTPContext存在一个问题,目前Typemock是唯一可以直接伪造的框架,没有任何包装或抽象。

caching对象很难模拟,因为它是.NET框架的一个密封区域。 我通常通过构build一个接受cachingpipe理器对象的caching包装类来解决这个问题。 为了testing,我使用了一个模拟cachingpipe理器; 对于生产我使用cachingpipe理器,实际上访问HttpRuntime.Cache。

基本上,我自己抽象出caching。

一个使用MVC 3和MOQ的例子:

我的控制器方法有以下行:

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

因此,任何unit testing都会失败,因为我没有设置HttpContext.Cache。

在我的unit testing中,我安排如下:

  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; 

可以试试…

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