2012-06-14 32 views
5

Đây là giải pháp/công việc xung quanh hơn câu hỏi thực tế. Tôi đang đăng nó ở đây vì tôi không thể tìm thấy giải pháp này trên tràn ngăn xếp hoặc thực sự sau rất nhiều Googling.Thử nghiệm đơn vị với mã EF Đầu tiên DataContext

Vấn đề:

Tôi có một MVC 3 webapp sử dụng EF 4 mã đầu tiên mà tôi muốn viết các unit test cho. Tôi cũng đang sử dụng NCrunch để chạy các bài kiểm tra đơn vị trên bay khi tôi viết mã, vì vậy tôi muốn tránh sao lưu vào một cơ sở dữ liệu thực tế ở đây.

Giải pháp khác:

IDataContext

tôi đã tìm thấy điều này một cách chấp nhận nhất để tạo ra một trong bộ nhớ DataContext. Nó có hiệu quả liên quan đến việc viết một giao diện IMyDataContext cho MyDataContext của bạn và sau đó sử dụng giao diện trong tất cả các bộ điều khiển của bạn. Một ví dụ về việc này là here.

Đây là tuyến đường tôi đã đi ban đầu và tôi thậm chí đã đi xa như viết một mẫu T4 để trích xuất IMyDataContext từ MyDataContext vì tôi không muốn phải duy trì mã phụ thuộc trùng lặp.

Tuy nhiên tôi nhanh chóng phát hiện ra rằng một số câu lệnh LINQ không thành công khi sử dụng IMyDataContext thay vì MyDataContext. Cụ thể truy vấn như thế này ném một NotSupportedException

var siteList = from iSite in MyDataContext.Sites 
       let iMaxPageImpression = (from iPage in MyDataContext.Pages where iSite.SiteId == iPage.SiteId select iPage.AvgMonthlyImpressions).Max() 
       select new { Site = iSite, MaxImpressions = iMaxPageImpression }; 

Giải pháp của tôi

này đã thực sự khá đơn giản. Tôi chỉ đơn giản là tạo một lớp con MyInMemoryDataContext để MyDataContext và gạt tất cả các IDbSet < ..> properties như sau:

public class InMemoryDataContext : MyDataContext, IObjectContextAdapter 
{ 
    /// <summary>Whether SaveChanges() was called on the DataContext</summary> 
    public bool SaveChangesWasCalled { get; private set; } 

    public InMemoryDataContext() 
    { 
     InitializeDataContextProperties(); 
     SaveChangesWasCalled = false; 
    } 

    /// <summary> 
    /// Initialize all MyDataContext properties with appropriate container types 
    /// </summary> 
    private void InitializeDataContextProperties() 
    { 
     Type myType = GetType().BaseType; // We have to do this since private Property.Set methods are not accessible through GetType() 

     // ** Initialize all IDbSet<T> properties with CollectionDbSet<T> instances 
     var DbSets = myType.GetProperties().Where(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>)).ToList(); 
     foreach (var iDbSetProperty in DbSets) 
     { 
      var concreteCollectionType = typeof(CollectionDbSet<>).MakeGenericType(iDbSetProperty.PropertyType.GetGenericArguments()); 
      var collectionInstance = Activator.CreateInstance(concreteCollectionType); 
      iDbSetProperty.SetValue(this, collectionInstance,null); 
     } 
    } 

    ObjectContext IObjectContextAdapter.ObjectContext 
    { 
     get { return null; } 
    } 

    public override int SaveChanges() 
    { 
     SaveChangesWasCalled = true; 
     return -1; 
    } 
} 

Trong trường hợp này CollectionDbSet tôi <> là một phiên bản sửa đổi nhẹ của FakeDbSet <>here (mà chỉ đơn giản thực hiện IDbSet với ObservableCollection và ObservableCollection.AsQueryable()).

Giải pháp này hoạt động độc đáo với tất cả các thử nghiệm đơn vị của tôi và đặc biệt với NCrunch chạy các thử nghiệm này khi đang di chuyển.

Full Tích hợp Tests

Các xét nghiệm Đơn vị kiểm tra tất cả các logic kinh doanh nhưng có một nhược điểm lớn là không ai trong số báo cáo LINQ của bạn được đảm bảo để làm việc với MyDataContext thực tế của bạn. Điều này là do thử nghiệm đối với bối cảnh dữ liệu bộ nhớ nghĩa là bạn đang thay thế nhà cung cấp LINQ-To-Entity nhưng nhà cung cấp LINQ-To-Objects (như đã chỉ ra rất rõ trong câu trả lời cho câu hỏi this SO).

Để khắc phục điều này, tôi sử dụng Ninject trong các bài kiểm tra đơn vị của mình và thiết lập InMemoryDataContext để liên kết thay vì MyDataContext trong các bài kiểm tra đơn vị của tôi. Sau đó bạn có thể sử dụng Ninject để liên kết với một MyDataContext thực tế khi chạy các kiểm tra tích hợp (thông qua một thiết lập trong app.config).

if(Global.RunIntegrationTest) 
    DependencyInjector.Bind<MyDataContext>().To<MyDataContext>().InSingletonScope(); 
else 
    DependencyInjector.Bind<MyDataContext>().To<InMemoryDataContext>().InSingletonScope(); 

Hãy cho tôi biết nếu bạn có bất kỳ phản hồi nào về việc này, luôn có những cải tiến.

+1

[Ở đây] (http://stackoverflow.com/questions/4128640) là một câu hỏi ngăn xếp về vấn đề này và [đây là một bài viết] (http://www.cuttingedge.it/blogs/steven/pivot/ entry.php? id = 84) mô tả một giải pháp khác cho vấn đề này. – Steven

+1

[Ở đây] (http://stackoverflow.com/questions/10967921/decouple-ef-queries-from-bl-extension-methods-vs-class-per-query) là một câu hỏi gần đây về cùng một chủ đề. –

Trả lời

3

Theo nhận xét của tôi trong câu hỏi, điều này giúp ích cho những người khác đang tìm kiếm sự cố này trên SO. Nhưng như đã chỉ ra trong các bình luận bên dưới câu hỏi, có một vài cách tiếp cận thiết kế khác có thể khắc phục vấn đề này.

Các vấn đề liên quan