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,当他们应该有调用的方法。
步骤(如果你只是想知道这个技术,而不需要挖掘大量的博客):
-
创build一个接口,描述你想要在你的caching中使用的方法(可能像GetItem,SetItem,ExpireItem)。 叫它ICache或任何你喜欢的
-
创build一个实现该接口的类,并将方法传递给实际的HttpContext
-
创build一个实现相同接口的类,就像一个模拟caching。 如果您关心保存对象,它可以使用字典或其他东西
-
改变你的原始代码,使它根本不使用HttpContext,而是只使用一个ICache。 代码将需要得到一个ICache的实例 – 你可以在你的类构造函数中传递一个实例(这就是所有的dependency injection),或者把它放在一些全局variables中。
-
在生产应用程序中,将ICache设置为您真正的HttpContext-Backed-Cache,并在您的unit testing中将ICache设置为模拟caching。
-
利润!
我同意其他人的观点,认为使用界面是最好的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一个例子。
这可能是你的街道…菲尔·哈克炫耀,在犀牛嘲笑的帮助下,如何在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");