2009-01-28 69 views
33

當爲使用HttpContext.Current.Cache class的類創建單元測試時,使用NUnit時出現錯誤。該功能是基本 - 檢查如果一個項目是在緩存中,如果沒有,創建它,並把它放在:單元測試HttpContext.Current.Cache或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"); 
} 

當從單元測試調用此,它失敗在遇到第CacheNullReferenceException線。在Java中,我將使用Cactus來測試服務器端代碼。有沒有類似的工具可以用於C#代碼? This SO question提到模擬框架 - 這是我可以測試這些方法的唯一方法嗎?有沒有類似的工具來運行C#測試?

此外,我不檢查Cache是否爲空,因爲我不想專門爲單元測試編寫代碼,並假定它在服務器上運行時始終有效。這是否有效,還是應該在緩存中添加空檢查?

回答

42

這樣做的方法是避免直接使用HttpContext或其他類似的類,並用mock替代它們。畢竟,你並沒有試圖測試HttpContext的功能是否正常(這是微軟的工作),你只是試圖在應該有的時候調用方法。

步驟(如果你只是想知道該技術無需通過博客負荷挖):

  1. 創建描述你想要在你的緩存使用的方法(可能之類的東西的GetItem的接口, SetItem,ExpireItem)。說它ICACHE或任何你喜歡

  2. 創建一個實現該接口的類,並通過對實際的HttpContext

  3. 創建一個實現相同接口的類傳遞方法,只是就像一個模擬的高速緩存。它可以使用字典或其他東西,如果你關心保存對象

  4. 改變你的原代碼,所以它根本不使用HttpContext,而是隻使用ICache。然後代碼需要獲得ICache的一個實例 - 您可以在類構造函數中傳遞一個實例(這就是所有依賴注入的實際內容),也可以將其放在某個全局變量中。

  5. 在您的生產應用程序中,將ICache設置爲您真正的HttpContext-Backed-Cache,並在您的單元測試中將ICache設置爲模擬緩存。

  6. 利潤!

2

一般認爲,在單元測試中推動任何與HttpContext相關的任何事情都是一場完全的噩夢,如果可能的話應該避免。

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

我讀過關於MoQ的一些好東西(http://code.google.com/p/moq),雖然我還沒有嘗試過。

如果你真的想編寫單元測試的Web UI在C#中,一路人似乎標題是使用MVC框架(http://www.asp.net/mvc),而不是Web窗體...

6

如果你使用.NET 3.5,你可以在你的應用程序中使用System.Web.Abstractions。

關於如何模擬HttpContext(其中包含緩存類),Justin Etheredge有一個很好的post

從Justin的例子中,我使用HttpContextFactory.GetHttpContext將HttpContextBase傳遞給我的控制器。當嘲笑它們時,我只是建立一個模擬來調用緩存對象。

+6

你可以嘲笑幾乎所有的HttpContextBase,但Cache屬性都不在其中。 HttpContextBase.Cache的類型是System.Web.Caching.Cache,它是密封的,在單元測試中不可用並且不可嘲弄... – chris166 2009-08-20 09:45:50

0

所有這些編程問題都需要一個基於接口的編程模型,在這個模型中你實現了兩次接口。一個用於真實代碼,一個用於模型。

實例化是下一個問題。有幾種可用於此的設計模式。例如參見着名的GangOfFour Creational模式(GOF)或依賴注入模式。

ASP.Net MVC實際上是使用這種基於接口的方法,因此更適合單元測試。

28

我同意其他人使用接口是最好的選擇,但有時候改變現有的系統是不可行的。這裏有一些代碼,我從我的一個項目中找到了一些代碼,這些代碼可以讓你找到你想要的結果。這是最美妙的解決方案,但如果你真的無法改變你的代碼,那麼它應該完成工作。

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

正如大家在這裏說,有一個與HttpContext的問題,目前Typemock是唯一的框架,可以僞造它直接沒有任何包裝或抽象。

1

這可能在你的街道上...... Phil Haack在Rhino mock的幫助下展示瞭如何在asp mvc中模擬httpcontext,但我想可以應用於webforms。

Clicky!!

希望這有助於。

0

緩存對象很難模擬,因爲它是.NET框架的一個密封區域。我通常通過構建一個接受緩存管理器對象的緩存包裝類來解決這個問題。爲了測試,我使用了模擬緩存管理器;對於生產,我使用實際訪問HttpRuntime.Cache的緩存管理器。

基本上,我自己抽象出緩存。

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

使用MVC 3和MOQ那些一個例子:

我的控制器方法具有下面的行:

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

這樣,任何單元測試將失敗,因爲我不設置up 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; 
5

有幫助的單元測試緩存專門處理一個較新的方法。

我會推薦使用微軟的新MemoryCache.Default的方法。您將需要使用.NET Framework 4.0或更高版本,幷包含對System.Runtime.Caching的引用。

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

Web和非Web應用程序MemoryCache.Default工作。所以你的想法是更新你的web應用程序來移除對HttpContext.Current.Cache的引用,並將它們替換爲對MemoryCache.Default的引用。稍後,當您運行決定單元測試這些相同的方法時,緩存對象仍然可用並且不會爲空。 (因爲它不依賴於HttpContext。)

這樣你甚至不一定需要模擬緩存組件。

1

,如果你不介意那些測試緩存護理,你可以做如下:

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

可以試試...

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