2012-02-20 61 views
2

我有一個應用程序,其中包含使用Entity Framework 4.2 Code First和MySQL數據庫處理數據的方法。我正在嘗試找出爲這些方法編寫MSTest單元測試的好方法。例如:如何爲使用實體框架4.2的代碼編寫單元測試?

的DataModel:

public class User 
{ 
    public User() { } 
    [Key] 
    public int UserID { get; set; } 
    public string Role { get; set; } 
} 

public class AppDbContext : DbContext 
{ 
    public DbSet<User> Users { get; set; } 
} 

業務層:

public class Bus 
{ 
    public bool UserIsInRole(int userID, string role) 
    { 
     using(var context = new AppDbContext()) 
     { 
      User user = context.Users.SingleOrDefault(p => p.UserID == userID); 
      if (user == null) 
       return false; 
      return user.Roles.Split(',').Contains(role); 
     } 
    } 
} 

我想寫一組單元測試的UserIsInRole功能,但我想嘗試自己從隔離實際上必須讀取和寫入實際的數據庫,因爲我無法在測試之前保證其狀態。僅僅爲這個測試設置/拆除數據庫將需要很長時間。

我遇到過很多關於使用假DbContext的文章,比如here,herehere,但它們都似乎有一些優點和缺點。一羣人說,不應該針對EF編寫單元測試,並且這屬於集成測試,並且任何假的DbContext都不能像可接受測試的目的那樣表現得足夠真實。

我認爲這樣的代碼位於參數中間的某個位置。理想情況下,我想創建一組表示所需數據的臨時內存對象,而無需將其實際存儲到數據庫中。

你將如何更改上面寫的一組驗證UserIsInRole方法測試:

  1. 返回false如果用戶ID中不存在的用戶收集 。
  2. 如果用戶不包含 所需的角色,則返回false。
  3. 如果用戶具有所需的角色,則返回true。

請記住,這是一個簡單的例子,該代碼實際上可以包含任意複雜的多個查詢,所以我希望能找到的東西有點比更全面,比如說,每個查詢移動到這是一個虛函數由測試框架替換以返回預定義的用戶記錄。

回答

2

我會從您的域的其餘部分分離知識的EF。如果你檢查DbSet,你會發現它實現了IQueryable,這足以讓EF工作。創建一個定義域範圍內的接口,使您的不同具體實現(EF和假)實現該接口,如:

IQueryable<User> GetUsersByManagerRole(IAppDomain domain) 
{ 
    return from u in domain.Users 
      where u.Role == "Manager" 
      select u; 
} 

這使您可以:

public class User 
{ 
    public User() { } 
    [Key] 
    public int UserID { get; set; } 
    public string Role { get; set; } 
} 

public interface IAppDomain 
{ 
    public IQueryable<User> Users { get; } 
} 

public class AppDbContext : DbContext, IAppDomain 
{ 
    // exposure for EF 
    public DbSet<User> Users { get; set; } 

    IAppDomain.IQueryable<User> Users { get { return ((AppDbContext)this).Users; } 
} 

public class FakeAppDomain : IAppDomain 
{ 
    private List<User> _sampleUsers = new List<User>(){ 
     new User() { UserID = 1, Role = "test" } 
    } 

    public IQueryable<User> Users { get { return _sampleUsers; } } 
} 

這可以像使用方式創建一個可以接受任何類型樣本輸入的假實現。接下來在你的單元測試中,你創建了一個新的FakeDomainContext,你可以用你想要的單元測試方式設置狀態。想要測試具有一定作用的用戶可以找到嗎?用具有某些測試角色的用戶創建一個FakeDomainContext並嘗試查找它們。簡單而乾淨。

+0

這是我正在考慮的事情。它將允許任何代碼從數據源創建所需的任何查詢。我擔心的是,查詢可能與在Linq到Entities之間返回的數據以及Linq到對象的(我認爲)有什麼不同。您對結果查詢兼容性有任何經驗嗎?例如,編譯良好並且通過測試但不能抵抗真實數據(區分大小寫,不受支持的函數,空行爲,結果分組)的情況。顯然,無論如何,都需要一定程度的集成測試。 – 2012-02-22 20:19:59

+0

我只是在我的代碼中嘗試了這種方法。但是,我使用的功能在IQueryable <>中不受支持,例如Create(),Add(),Remove()等。它看起來像我還需要爲每個實體創建某種接口,或者將這些移動到IAppDomain接口。 – 2012-02-22 21:07:49

+0

@DanC - 你也可以公開IDbSet而不是IQueryable。這給你所有他的選擇,但允許你創建一個虛假的實現 – Polity 2012-02-23 02:07:28

0

如果你不喜歡嘲笑的DbContext,你可以嘲笑的數據對象,並提取了一些理財週報的邏輯爲DB-獨立的可測試方法。

public class Bus 
{ 
    private UnitTestable impl; 

    public bool UserIsInRole(int userID, string role) 
    { 
     using(var context = new AppDbContext()) 
     { 
      return impl.UserInRole(context.Users, role); 
     } 
    } 
} 

public class UnitTestable 
{ 
    public bool UserInRole(IQueryable<User> users, string role) 
    { 
     User user = users.SingleOrDefault(p => p.UserID == userID); 
     if (user == null) 
      return false; 
     return user.Roles.Split(',').Contains(role); 
    } 
} 

要嘲笑測試中的用戶對象,您可能需要使其屬性變爲虛擬或從中提取一個接口。

0

我個人認爲像IsUserInRole這樣的函數屬於業務邏輯而不是數據存儲,但是我看到很多問題之一是開發人員經常將這些東西緊緊地耦合在一起。在類似IsUserInRole的情況下,實現通常是查找用戶所屬的所有角色,並查看用戶是否處於指定角色。這意味着,如果沒有附加某種數據庫,您實際上無法測試這部分功能。

我個人認爲這個問題的答案是使用存儲庫模式從數據庫的特定實現中解耦代碼。這使您可以模擬存儲庫而無需重構代碼。我知道這在技術上有可能通過DBContext模擬來實現,但我更喜歡存儲庫,因爲它們與特定類型的數據存儲機制緊密耦合,即在此示例中不與EF綁定。

下面是我的博客鏈接,以及我個人如何解決問題。我所做的事情一直擔任我很好所以來看看的例子,我是如何實現的單元測試與後端數據存儲到EF 4

http://blog.staticvoid.co.nz/2011/10/staticvoid-repository-pattern-nuget.html

3

你的代碼是不是測試。如果您在被測系統中直接使用new,您如何僞造某些東西?

提高代碼:

public class Bus 
{ 
    public bool UserIsInRole(int userID, string role) 
    { 
     using(var context = CreateContext()) 
     { 
      User user = ExecuteGetUserQuery(context, userId); 
      if (user == null) 
       return false; 
      return user.Roles.Split(',').Contains(role); 
     } 
    } 

    protected virtual IAppDbContext CreateContext() 
    { 
     return new AppDbContext(); 
    } 

    protected virtual User ExecuteGetUserQuery(IAppDbContext context, int userId) 
    { 
     return context.Users.SingleOrDefault(p => p.UserID == userID); 
    } 
} 

現在不引入任何新的類(只是單一的界面爲你的背景下),我們使你的代碼可測試:

  • 首先我們所做的是將單一職責原則並將您的方法分爲三個單獨的職責:
    • 上下文創建
    • 查詢執行
    • UserIsInRole邏輯
  • 我們增加了界面背景,讓您的邏輯依賴於抽象而不是實現
  • 我們還增加了一些掛鉤於測試

替換實施當你想編寫單元測試UserIsInRole(和其他純粹的單元測試),您可以使測試派生實現Bus類,並返回來自ExecuteGetUserQuery的替代版本的任何假數據。通過覆蓋CreateContext,您還將使您的測試完全獨立於數據庫或EF。重寫這些方法不會導致測試不同的邏輯,因爲無論如何你都會僞造這些數據。測試的UserIsInRole方法在派生類中未被修改。

當然,不是提供虛擬方法,您可以將此功能移入單獨的一個或多個類中,並使用存根或嘲笑,但對於簡單場景這可行。如果您需要測試與數據庫的交互,則只會編寫集成測試ExecuteGetUserQuery

+0

謝謝拉迪斯拉夫。我實際上希望找到避免將每個查詢移動到其自己的虛擬函數的解決方案,因爲可以有許多不同的查詢,連接數據,過濾等等,並且我希望在數據庫中做同樣的事情,而不必返回整個數據集。 (我意識到你的例子是單元測試重構101.我的生產代碼確實做了類似的事情,但我希望有更多的高級EF特定的替代品)。 – 2012-02-22 20:04:40

+1

EF特定的單元測試替代[不存在](http://stackoverflow.com/questions/6904139/fake-dbcontext-of-entity-framework-4-1-to-test/6904479#6904479)。人們以某種​​方式尋找測試EF代碼的方式,而不考慮他們將要測試的方法。正確的單元測試要求你的測試代碼只做一件事,所以如果你想正確地做到這一點,你最終會用不同的方法進行查詢。否則,您的測試將會過於複雜且難以維護。 – 2012-02-22 20:17:57

相關問題