![](/img/trans.png)
[英]Having problems with an ASP.NET MVC unit test & HttpContext.Current.Cache
[英]Unit test HttpContext.Current.Cache or other server-side methods in C#?
為使用HttpContext.Current.Cache
類的類創建單元測試時,使用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");
}
從單元測試調用此NullReferenceException
時,遇到第一個Cache
行時,它將失敗並顯示NullReferenceException
。 在Java中,我將使用Cactus測試服務器端代碼。 我可以將類似的工具用於C#代碼嗎? 這個SO問題提到了模擬框架-這是我測試這些方法的唯一方法嗎? 有沒有類似的工具可以為C#運行測試?
另外,我不檢查Cache
是否為空,因為我不想專門為單元測試編寫代碼,並假定在服務器上運行時它將始終有效。 這是否有效,還是應該在緩存周圍添加空檢查?
這樣做的方法是避免直接使用HttpContext或其他類似的類,並用模擬代替它們。 畢竟,您不是要測試HttpContext是否正常運行(這是Microsoft的工作),而是只是測試這些方法在應有的時候被調用了。
步驟(如果您只是想了解該技術而又不涉及大量博客):
創建一個接口,該接口描述要在緩存中使用的方法(可能是諸如GetItem,SetItem,ExpireItem之類的東西)。 稱它為ICache或任何你喜歡的東西
創建一個實現該接口的類,並將方法傳遞給實際的HttpContext
創建一個實現相同接口的類,就像一個模擬緩存一樣。 如果您想保存對象,它可以使用字典或類似的東西
更改您的原始代碼,使其完全不使用HttpContext,而只使用ICache。 然后,代碼將需要獲取ICache的實例-您可以在類構造函數中傳遞實例(這就是依賴注入的全部內容),或將其粘貼在某些全局變量中。
在生產應用程序中,將ICache設置為真正的HttpContext-Backed-Cache,在單元測試中,將ICache設置為模擬緩存。
利潤!
我同意其他人的觀點,即使用接口是最好的選擇,但是有時更改現有系統只是不可行的。 這是我剛剛從我的一個項目中合並在一起的一些代碼,這些代碼應該可以為您提供所需的結果。 這是一個不錯的解決方案,它是最遙遠的事情,但是如果您真的無法更改代碼,那么它應該可以完成工作。
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));
有一種更新的方法可以幫助專門處理單元測試中的緩存。
我建議使用Microsoft的新MemoryCache.Default方法。 您將需要使用.NET Framework 4.0或更高版本,並包括對System.Runtime.Caching的引用。
在此處查看文章-> http://msdn.microsoft.com/zh-cn/library/dd997357(v=vs.100).aspx
MemoryCache.Default適用於Web和非Web應用程序。 因此,您的想法是更新Web應用程序以刪除對HttpContext.Current.Cache的引用,並將其替換為對MemoryCache.Default的引用。 稍后,當您運行決定對這些相同的方法進行單元測試時,緩存對象仍然可用,並且不會為空。 (因為它不依賴於HttpContext。)
這樣,您甚至不必模擬緩存組件。
普遍的共識似乎是,在單元測試中驅動與HttpContext相關的任何事情都是一場噩夢,應該盡可能避免。
我認為您在模擬方面走了正確的路。 我喜歡RhinoMocks( http://ayende.com/projects/rhino-mocks.aspx )。
我也閱讀了有關MoQ的一些好東西( http://code.google.com/p/moq ),盡管我還沒有嘗試過。
如果您真的想用C#編寫可單元測試的Web UI,人們似乎會使用MVC框架( http://www.asp.net/mvc )而不是WebForms。
您可以在System.Web.Abstractions.dll中使用HttpContextBase類。 這是.NET 3.5中的新dll。
您可以在下面的鏈接中找到如何使用的示例。
如果您不關心測試緩存,則可以執行以下操作:
[TestInitialize]
public void TestInit()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
}
你也可以像下面一樣起訂量
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"));
對於那些使用MVC 3和MOQ的示例:
我的控制器方法有以下幾行:
model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList]
as Dictionary<int, string>);
因此,任何單元測試都將失敗,因為我沒有設置HttpContext.Cache。
在單元測試中,我安排如下:
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");
所有這些編程問題都要求一個基於接口的編程模型,在該模型中您需要兩次實現該接口。 一種用於真實代碼,另一種用於模型。
實例化是下一個問題。 有幾種設計模式可用於此目的。 例如,參見著名的GangOfFour Creational模式( GOF )或Dependency Injection模式。
實際上,ASP.Net MVC使用的是這種基於接口的方法,因此更適合於單元測試。
就像每個人都說的那樣, HTTPContext存在一個問題,當前Typemock是唯一可以直接偽造它而無需任何包裝或抽象的框架。
緩存對象很難模擬,因為它是.NET框架的密封區域。 我通常通過構建一個接受緩存管理器對象的緩存包裝器類來解決此問題。 為了進行測試,我使用了模擬緩存管理器; 在生產中,我使用一個實際上訪問HttpRuntime.Cache的緩存管理器。
基本上,我自己提取緩存。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.