2009-01-28 162 views
33

Khi tạo kiểm tra đơn vị cho một lớp sử dụng HttpContext.Current.Cache class, tôi gặp lỗi khi sử dụng NUnit. Các chức năng là cơ bản - kiểm tra xem một mục trong bộ nhớ cache, và nếu không, tạo ra nó và đặt nó trong:Kiểm tra đơn vị HttpContext.Current.Cache hoặc các phương thức phía máy chủ khác trong 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"); 
} 

Khi gọi này từ một thử nghiệm đơn vị, nó không thành công với ít NullReferenceException khi gặp phải các Cache đầu tiên hàng. Trong Java, tôi sẽ sử dụng Cactus để kiểm tra mã phía máy chủ. Có một công cụ tương tự tôi có thể sử dụng cho mã C#? This SO question đề cập đến khung mock - đây có phải là cách duy nhất tôi có thể kiểm tra các phương pháp này không? Có một công cụ tương tự để chạy thử nghiệm cho C#?

Ngoài ra, tôi không kiểm tra nếu Cache là null vì tôi không muốn viết mã đặc biệt cho thử nghiệm đơn vị và giả sử nó sẽ luôn hợp lệ khi chạy trên máy chủ. Đây có phải là hợp lệ, hoặc tôi nên thêm kiểm tra null xung quanh bộ nhớ cache?

Trả lời

42

Cách để thực hiện điều này là tránh sử dụng trực tiếp lớp HttpContext hoặc các lớp tương tự khác và thay thế chúng bằng mocks. Sau khi tất cả, bạn không cố gắng để kiểm tra rằng các chức năng HttpContext đúng (đó là công việc của microsoft), bạn chỉ đang cố gắng để kiểm tra rằng các phương pháp đã được gọi là khi họ cần phải có.

bước (Trong trường hợp bạn chỉ muốn biết kỹ thuật này mà không cần đào bới vô số blog):

  1. Tạo một giao diện trong đó mô tả các phương pháp bạn muốn sử dụng trong bộ nhớ đệm của bạn (có thể là những thứ như GetItem, SetItem, ExpireItem). Gọi đó là ICache hoặc bất cứ điều gì bạn thích

  2. Tạo một lớp mà thực hiện giao diện đó, và vượt qua các phương pháp thông qua các HttpContext thực

  3. Tạo một lớp mà thực hiện cùng một giao diện, và chỉ hoạt động như một bộ nhớ cache giả. Nó có thể sử dụng một từ điển hoặc một cái gì đó nếu bạn quan tâm đến việc lưu các đối tượng

  4. Thay đổi mã ban đầu của bạn để nó không sử dụng HttpContext, và thay vào đó chỉ sử dụng ICache.Đoạn mã sau đó sẽ cần để có được một thể hiện của ICache - bạn có thể truyền một thể hiện trong constructor lớp của bạn (đây là tất cả sự tiêm phụ thuộc thực sự), hoặc dính nó vào một biến toàn cầu nào đó.

  5. Trong ứng dụng sản xuất của bạn, hãy đặt ICache thành HttpContext-Backed-Cache thực sự của bạn và trong các bài kiểm tra đơn vị của bạn, đặt ICache thành bộ nhớ cache giả.

  6. Lợi nhuận!

2

Sự đồng thuận chung dường như là lái xe bất cứ điều gì HttpContext liên quan từ bên trong một bài kiểm tra đơn vị là một cơn ác mộng, và nên tránh nếu có thể.

Tôi nghĩ bạn đang đi đúng hướng về chế nhạo. Tôi thích RhinoMocks (http://ayende.com/projects/rhino-mocks.aspx).

Tôi đã đọc một số điều hay về MoQ quá (http://code.google.com/p/moq), mặc dù tôi chưa thử.

Nếu bạn thực sự muốn viết đơn vị kiểm chứng web UI trong C#, cách mọi người dường như được nhóm là sử dụng khuôn khổ MVC (http://www.asp.net/mvc) chứ không phải là WebForms ...

6

Nếu bạn đang sử dụng .NET 3.5, bạn có thể sử dụng System.Web.Abstractions trong ứng dụng của mình.

Justin Etheredge có số lượng lớn post về cách giả lập HttpContext (chứa lớp bộ nhớ cache).

Từ ví dụ của Justin, tôi chuyển một HttpContextBase tới bộ điều khiển của mình bằng cách sử dụng HttpContextFactory.GetHttpContext. Khi chế nhạo chúng, tôi chỉ xây dựng một Mock để thực hiện cuộc gọi đến đối tượng bộ nhớ cache.

+6

Bạn có thể thử hầu hết mọi thứ với HttpContextBase, nhưng thuộc tính Cache không nằm trong số đó. HttpContextBase.Cache là loại System.Web.Caching.Cache được niêm phong, không sử dụng được trong các bài kiểm tra đơn vị và không thể nhại được ... – chris166

0

Tất cả các câu hỏi lập trình này yêu cầu mô hình lập trình dựa trên giao diện, trong đó bạn triển khai giao diện hai lần. Một cho mã thực và một cho mockup.

Việc giải thích là vấn đề tiếp theo. Có một số mẫu thiết kế có thể được sử dụng cho điều đó. Xem ví dụ các mẫu Sáng tạo GangOfFour nổi tiếng (GOF) hoặc các mẫu Chèn phụ thuộc.

ASP.Net MVC thực tế là sử dụng phương pháp tiếp cận dựa trên giao diện này và do đó phù hợp hơn với thử nghiệm đơn vị.

28

Tôi đồng ý với những người khác sử dụng giao diện sẽ là lựa chọn tốt nhất nhưng đôi khi không thể thay đổi hệ thống hiện tại. Dưới đây là một số mã mà tôi vừa mới trộn với nhau từ một trong các dự án của tôi sẽ cung cấp cho bạn kết quả mà bạn đang tìm kiếm. Đó là điều xa vời nhất từ ​​một giải pháp tuyệt vời hoặc tuyệt vời nhưng nếu bạn thực sự không thể thay đổi mã của mình thì nó sẽ hoàn thành công việc.

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

Như mọi người đều nói ở đây, có một vấn đề với HttpContext, hiện Typemock là khuôn khổ duy nhất mà có thể giả mạo nó trực tiếp mà không cần bất kỳ giấy gói hoặc trừu tượng.

1

Điều này có thể lên đường phố của bạn ... Phil Haack khoe khoang, với sự giúp đỡ của tê giác tê giác, làm thế nào để giả lập httpcontext trong asp mvc nhưng tôi tưởng tượng có thể được áp dụng cho webforms.

Clicky!!

Hy vọng điều này sẽ hữu ích.

0

Đối tượng bộ nhớ cache khó giả lập vì đó là vùng kín của khung .NET. Tôi thường làm được điều này bằng cách xây dựng một lớp trình bao bọc bộ nhớ cache chấp nhận một đối tượng trình quản lý bộ nhớ cache. Để thử nghiệm, tôi sử dụng trình quản lý bộ nhớ cache giả; cho sản xuất, tôi sử dụng một trình quản lý bộ nhớ cache thực sự truy cập HttpRuntime.Cache.

Về cơ bản, tôi tự mình xóa bộ nhớ cache.

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

Một ví dụ cho những người sử dụng MVC 3 và MOQ:

phương pháp điều khiển của tôi có các dòng sau:

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

Như vậy, bất kỳ đơn vị kiểm tra sẽ thất bại như tôi không thiết lập lên HttpContext.Cache.

Trong thử nghiệm đơn vị của tôi, tôi sắp xếp như sau:

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

Có một cách tiếp cận mới để giúp đối phó cụ thể với bộ nhớ cache trong các thử nghiệm đơn vị.

Tôi khuyên bạn nên sử dụng phương pháp tiếp cận mới MemoryCache.Default mới của Microsoft. Bạn sẽ cần phải sử dụng .NET Framework 4.0 hoặc mới hơn và bao gồm một tham chiếu đến System.Runtime.Caching.

Xem bài viết ở đây ->http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default làm việc cho cả web và phi các ứng dụng web. Vì vậy, ý tưởng là bạn cập nhật webapp của bạn để loại bỏ các tham chiếu đến HttpContext.Current.Cache và thay thế chúng bằng các tham chiếu đến MemoryCache.Default. Sau đó, khi bạn chạy quyết định Unit Test cùng các phương thức này, đối tượng cache vẫn có sẵn và sẽ không rỗng. (Bởi vì nó không phụ thuộc vào một HttpContext.)

Bằng cách này bạn thậm chí không nhất thiết cần phải giả lập thành phần bộ nhớ cache.

1

nếu bạn không chăm sóc về thử nghiệm bộ nhớ cache bạn có thể làm dưới đây:

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

Ngoài ra bạn có thể MOQ như dưới đây

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

thể thử ...

Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake(); 
var fakeSession = HttpContext.Current.Session; 
Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1"); 
Các vấn đề liên quan