52

My previous question讓我再次思考層,存儲庫,依賴注入和像這樣的架構。ASP.NET MVC3和實體框架代碼第一架構

我的架構現在看起來像這樣:
我首先使用EF代碼,所以我只是創建了POCO類和上下文。這創建了數據庫和模型。
級別更高的是業務層類(提供者)。我爲每個域使用不同的提供者...像MemberProvider,RoleProvider,TaskProvider等,我正在這些提供者中創建我的DbContext的新實例。
然後我在我的控制器中實例化這些提供程序,獲取數據並將它們發送到Views。

我最初的架構包含了倉庫,我擺脫了倉庫,因爲我被告知它只是增加了複雜性,所以爲什麼我不僅僅使用EF。我想這樣做..直接從控制器使用EF,但是我必須編寫測試,而且它與真實數據庫有點複雜。我不得不假冒 - 莫名其妙的數據。所以我爲每個提供者做了一個接口,並在列表中僞造了硬編碼數據。然後,我回到了某個地方,我不知道如何正確進行。

這些事情開始過於複雜得太快...許多方法和「pat」「......它創造了太多的噪音和無用的代碼。

是否有任何簡單和可測試的體系結構來創建實體框架和ASP.NET MVC3應用程序?

+0

你說庫增加了「複雜」到你的應用程序,但我要說他們是最初的'開銷',使測試更容易。嘲笑一些存儲庫比嘲笑整個數據上下文更容易。 – Omar 2011-04-10 03:40:12

+0

是的,但我不想在我目前的情況下開始的開銷。我想快速申請進度。我已經失去了太多時間,沒有任何實際進展。添加存儲庫會帶來像IoC,DI等類似的東西。在我開始查看之前,我將不得不編寫大量的測試。我知道這可能是正確的解決方案,但我不是在尋找「正確的」。我正在尋找簡單的(雖然仍然可測試)的解決方案。 – Damb 2011-04-10 03:46:07

回答

94

如果您想要使用TDD(或任何其他高測試覆蓋率的測試方法)和EF,您必須編寫集成或端到端測試。這裏的問題是,任何模擬上下文或存儲庫的方法都會創建測試,它可以測試您的上層邏輯(使用這些模擬)而不是您的應用程序。

簡單的例子:

讓我們定義通用的存儲庫:

public interface IGenericRepository<TEntity> 
{ 
    IQueryable<TEntity> GetQuery(); 
    ... 
} 

並讓寫一些商業方法:

public IEnumerable<MyEntity> DoSomethingImportant() 
{ 
    var data = MyEntityRepo.GetQuery().Select((e, i) => e); 
    ... 
} 

現在,如果你嘲笑的庫中,您將使用的LINQ到 - 對象和你將有一個綠色的測試,但如果你使用Linq-To-Entities運行應用程序,你將會得到一個異常,因爲在L2E中不支持索引的選擇過載。

這是一個簡單的例子,但在查詢和其他常見錯誤中使用方法會發生同樣的情況。此外,這也會影響像Add,Update和Delete這樣的方法,這些方法通常暴露在資源庫上如果你不寫一個模擬真正模擬EF上下文和參照完整性的行爲,你將不會測試你的實現。

故事的另一部分是懶惰加載的問題,單元測試對於嘲笑也很難檢測到。

因此,您還應該引入集成或端到端測試,這些測試將使用真實EF環境L2E與真實數據庫一起工作。順便說一句。需要使用端到端測試才能正確使用TDD。要在ASP.NET MVC中編寫端到端測試,您可以使用WatiN,也可以使用SpecFlow來處理BDD,但這樣做確實會增加很多工作量,但是您將會真正測試應用程序。如果您想了解更多關於TDD的信息,我建議您使用this book(唯一的缺點就是Java中的例子)。

集成測試如果不使用通用存儲庫,並且隱藏了某些不會暴露IQueryable但直接返回數據的類中的查詢,那麼這種測試是有意義的。

例子:

public interface IMyEntityRepository 
{ 
    MyEntity GetById(int id); 
    MyEntity GetByName(string name); 
} 

現在你可以只寫集成測試,測試執行這個倉庫的,因爲查詢隱藏在這個類,而不是暴露到上層。但是這種類型的存儲庫在某種程度上被認爲是與存儲過程一起使用的舊實現。這種實現會失去很多ORM特性,或者你將不得不做很多額外的工作 - 例如,添加specification pattern以便能夠在上層定義查詢。

在ASP.NET MVC中,您可以用控制器級別的集成測試部分替換端到端測試。

編輯基於評論:

我不說,你需要的單元測試,集成測試和終端到終端的測試。我說做測試的應用程序需要更多的努力。所需測試的數量和類型取決於應用程序的複雜程度,應用程序的預期未來,其他團隊成員的技能和技能。

小型直接項目可以在沒有測試的情況下創建(好吧,這不是一個好主意,但我們都做到了,並且最終它可以工作),但是一旦項目通過某個閾值,您就會發現引入新功能或維護這個項目非常困難,因爲你永遠不知道它是否會破壞已經發揮作用的東西 - 這就是所謂的迴歸。防止迴歸的最佳防禦措施是一套很好的自動化測試。

  • 單元測試可以幫助您測試方法。理想情況下,這種測試應該覆蓋方法中的所有執行路徑。這些測試應該非常簡短並且易於編寫 - 複雜的部分可以是設置依賴關係(嘲諷,僞造,存根)。
  • 集成測試可幫助您測試跨多個層的功能,並且通常跨多個進程(應用程序,數據庫)進行測試。你不需要爲他們提供所有的東西,而是更多地選擇他們有用的地方。
  • 端到端測試類似於用例/用戶故事/功能驗證。他們應該涵蓋整個流程的要求。

不需要多次測試feture - 如果您知道該功能是在端到端測試中測試的,則無需爲同一代碼編寫集成測試。此外,如果您知道該方法僅具有集成測試所涉及的單一執行路徑,則無需爲其編寫單元測試。 TDD方法的效果要好得多,您可以從一個大測試(端到端或集成)開始,然後深入單元測試。

根據您的開發方法,您不必從頭開始進行多種類型的測試,但隨着應用程序變得越來越複雜,您可以稍後介紹它們。 TDD/BDD是個例外,你甚至在寫單行其他代碼之前,應該至少開始使用端到端和單元測試。

所以你問的是錯誤的問題。問題不是更簡單?問題是最終會對你有什麼幫助,什麼複雜性適合你的應用程序?如果您想要輕鬆地進行單元測試的應用程序和業務邏輯,您應該將EF代碼封裝到可以被模擬的其他類中。但在同一時間,您必須引入其他類型的測試以確保EF代碼正常工作。

我不能說你用什麼辦法將適合你的環境/項目/團隊/等,但我可以從我過去的項目解釋例如:

我工作的項目約5-6個月有兩個的同事。該項目基於ASP.NET MVC 2 + jQuery + EFv4,並且是以增量和迭代的方式開發的。它有很多複雜的業務邏輯和大量複雜的數據庫查詢。我們從通用存儲庫和高代碼覆蓋率開始,使用單元測試+集成測試來驗證映射(插入,刪除,更新和選擇實體的簡單測試)。幾個月後,我們發現我們的方法不起作用。我們有超過1.200個單元測試,代碼覆蓋率約爲60%(這不是很好)以及很多回歸問題。改變EF模型中的任何內容都可能在數週內未觸及的部分引入意想不到的問題。我們發現我們缺少對我們的應用程序邏輯進行的集成測試或端對端測試。關於另一個項目的平行團隊也得出了同樣的結論,並且使用集成測試被認爲是新項目的建議。

+0

嗯。 。所以如果我理解正確的話,你說使用mock是用於業務邏輯的單元測試,我需要用真正的ef上下文以及端到端測試來進行集成測試(我將它理解爲功能/用戶測試。用工具li ke Watin)。但我沒有看到關於建築的觀點。我很高興你給出了有關問題的提示,但我在這個領域沒有經驗,所以我不知道什麼是更好的解決方案。這就是我在這裏尋找的。而我正在談論「更簡單」或「更簡單」的意思。 – Damb 2011-04-10 13:35:55

+0

謝謝。我非常感謝你的迴應和解釋。我相信我現在正在使用「將ef代碼包裝到其他類」(我的Provider類)。只是爲我的問題添加一些上下文:我正在爲用戶創建一個圍繞任務(在項目上下文中)管理的簡單應用程序(+專家系統,不會改變體系結構,因爲它只是消耗數據並提供簡單的輸出)。這是我自己的項目(沒有其他人正在研究它),我不認爲它會有很好的未來。 – Damb 2011-04-10 16:29:20

+1

@Ladislav:有可能靜態代碼分析工具可以捕獲類似於你所描述的問題(Linq to Entities不支持的Linq方法)?如果是這樣,那麼你可以在不必爲他們編寫單元測試的情況下消除一類錯誤,並且更加確信你所編寫的測試中的模擬將「真正起作用」。它可能無法解決參考完整性問題,但正如您所說,這些可以通過集成測試(而不是E2E)來處理。 – 2011-09-19 12:05:57

13

使用存儲庫模式會增加複雜性嗎?在你的情況下,我不這麼認爲。它使TDD更容易,代碼更易於管理。嘗試使用通用存儲庫模式以獲得更多分離和更乾淨的代碼。

如果您想了解更多關於實體框架TDD和設計模式,來看看:http://msdn.microsoft.com/en-us/ff714955.aspx

但是好像你正在尋找一種方法來模擬測試實體框架。一種解決方案是使用虛擬種子方法來生成數據庫初始化數據。看看種子部分在:http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx

也可以使用一些模擬框架。最有名的,我知道有:

要查看.NET框架嘲諷的更完整列表,請訪問:https://stackoverflow.com/questions/37359/what-c-mocking-framework-to-use

另一種方法是使用像SQLite這樣的內存數據庫提供程序。學習更多Is there an in-memory provider for Entity Framework?

最後,這裏是關於單元測試實體框架的一些很好的鏈接(一些鏈接指的是Entity Framework 4.0,但你會明白的。):

http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/678b5871-bec5-4640-a024-71bd4d5c77ff

http://mosesofegypt.net/post/Introducing-Entity-Framework-Unit-Testing-with-TypeMock-Isolator.aspx

What is the way to go to fake my database layer in a unit test?

+0

感謝您的輸入,有一些有趣的鏈接在那裏。但是我的問題其實並不是那麼多關於測試和模擬。這更多的是尋找簡單,快速,簡單的架構,無需開銷。您可以使用它來快速輕鬆地測試和開發應用程序,而無需準備XYZ代碼行以查看,如果您的方法確實返回字符串值。 {Sry,有點諷刺。} – Damb 2011-04-10 04:52:34

+0

@dampe:好吧,我沒有編寫接口和手動模擬數據,而是提出了一些額外的解決方案,可以爲你做很多工作。再一次,我會在這些情況下使用通用的存儲庫模式,並且從不覺得這會增加我的解決方案的複雜性。希望能幫助到你。 – Kamyar 2011-04-10 05:00:31

+0

有關通用存儲庫的建議,請參閱本教程:http://www.asp.net/entity-framework/tutorials/implementing-the-repository-and-unit-of-work-patterns-in-an-asp -net-mvc-application – tdykstra 2011-04-12 22:10:16

1

我有同樣的問題,決定在我的MVC應用程序的通用設計。 Shiju Varghese的CodePlex項目很有幫助。它在ASP.net MVC3,EF CodeFirst中完成,並且還使用服務層和存儲庫層。依賴注入是使用Unity完成的。這很簡單,很容易遵循。它也支持4個非常好的博客文章。它值得檢查。而且,不要放棄存儲庫..是的。

+2

謝謝,我看了解解決方案的代碼,它幾乎不存在我不想要的東西......所有的存儲庫,IoC,工廠等等。這不是我想象的當有人說「簡單的架構「:) – Damb 2011-04-10 13:27:31

+0

我可以建議的最簡單的設計(雖然不推薦)是直接從您的控制器創建EF上下文對象,但正如您的問題中指出的那樣,您已經嘗試過,並且已經遇到問題了.. – Ben 2011-04-10 16:42:04

2

我所做的是我使用一個簡單的ISession和EFSession對象,女巫很容易在我的控制器中模擬,輕鬆訪問Linq和強類型。使用Ninject注入DI。

public interface ISession : IDisposable 
    { 
     void CommitChanges(); 
     void Delete<T>(Expression<Func<T, bool>> expression) where T : class, new(); 
     void Delete<T>(T item) where T : class, new(); 
     void DeleteAll<T>() where T : class, new(); 
     T Single<T>(Expression<Func<T, bool>> expression) where T : class, new(); 
     IQueryable<T> All<T>() where T : class, new(); 
     void Add<T>(T item) where T : class, new(); 
     void Add<T>(IEnumerable<T> items) where T : class, new(); 
     void Update<T>(T item) where T : class, new(); 
    } 

public class EFSession : ISession 
    { 
     DbContext _context; 

     public EFSession(DbContext context) 
     { 
      _context = context; 
     } 


     public void CommitChanges() 
     { 
      _context.SaveChanges(); 
     } 

     public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() 
     { 

      var query = All<T>().Where(expression); 
      foreach (var item in query) 
      { 
       Delete(item); 
      } 
     } 

     public void Delete<T>(T item) where T : class, new() 
     { 
      _context.Set<T>().Remove(item); 
     } 

     public void DeleteAll<T>() where T : class, new() 
     { 
      var query = All<T>(); 
      foreach (var item in query) 
      { 
       Delete(item); 
      } 
     } 

     public void Dispose() 
     { 
      _context.Dispose(); 
     } 

     public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() 
     { 
      return All<T>().FirstOrDefault(expression); 
     } 

     public IQueryable<T> All<T>() where T : class, new() 
     { 
      return _context.Set<T>().AsQueryable<T>(); 
     } 

     public void Add<T>(T item) where T : class, new() 
     { 
      _context.Set<T>().Add(item); 
     } 

     public void Add<T>(IEnumerable<T> items) where T : class, new() 
     { 
      foreach (var item in items) 
      { 
       Add(item); 
      } 
     } 

     /// <summary> 
     /// Do not use this since we use EF4, just call CommitChanges() it does not do anything 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="item"></param> 
     public void Update<T>(T item) where T : class, new() 
     { 
      //nothing needed here 
     } 

如果我想從EF4切換到讓我們說MongoDB中,我只需要做的是實現一個的Isession ... MongoSession

+0

謝謝。我相信我正在做類似的事情......除了泛型和Ninject部分:) – Damb 2011-04-10 17:32:03

相關問題