2016-05-17 59 views
1

我試圖在服務中的查詢中測試業務邏輯。所以我不希望我的測試能夠真正訪問數據庫,因爲它們是單元測試,而不是集成測試。使用實體框架而不依賴注入的測試服務

所以我已經做了一個簡單的例子,我的上下文,以及我如何試圖填補它。

我有一個實體

public class SomeEntity 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

和服務

public class Service 
{ 
    public int CountSomeEntites() 
    { 
     using (var ctx = new Realcontext()) 
     { 
      int result = ctx.SomeEntities.Count(); 
      return result; 
     } 
    } 
} 

而這纔是真正的背景下

public partial class Realcontext : DbContext 
{ 
    public virtual DbSet<SomeEntity> SomeEntities { get; set; } 

    public Realcontext() : base("name=Realcontext") 
    { 
     InitializeContext(); 
    } 

    partial void InitializeContext(); 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     throw new UnintentionalCodeFirstException(); 
    } 
} 

所以我試圖創建一個假背景和我在我的測試方法中虛構了真實環境的構造函數

這是假的情況下

public class FakeContext : DbContext 
{ 
    public DbSet<SomeEntity> SomeEntities { get; set; } 

    public FakeContext() 
    { 
    } 
} 

最後的測試類

[TestClass] 
public class ServiceTests 
{ 
    [TestMethod] 
    public void CountEmployee_ShoulReturnCorrectResult() 
    { 
     using (ShimsContext.Create()) 
     { 
      ShimRealcontext.Constructor = context => GenerateFakeContext(); 
      ShimDbContext.AllInstances.Dispose =() => DummyDispose(); 

      Service service = new Service(); 
      int result = service.CountSomeEntites(); 

      Assert.AreEqual(result, 2); 
     } 
    } 

    private FakeContext GenerateFakeContext() 
    { 
     FakeContext fakeContext = new FakeContext(); 
     fakeContext.SomeEntities.AddRange(new[] 
     { 
      new SomeEntity {Id = 1, Name = "entity1"}, 
      new SomeEntity {Id = 2, Name = "entity2"} 
     }); 
     return fakeContext; 
    } 
} 

當我運行測試時,RealContext構造正常返回,一個FakeContext建在GenerateFakeContext()方法,它包含2 SomeEntities並返回,但在服務之後,變量ctx的屬性SomeEntities等於空。

是因爲我的變量ctx被聲明爲new RealContext()?但調用RealContext的構造函數會返回FakeContext(),那麼該變量應該是FakeContext類型的變量嗎?

我做錯了什麼?還是有沒有其他的方式來測試服務而不訪問真正的數據庫?

+0

在我們的測試中,我們借鑑了EF測試源中的模擬。它在Apache許可證下。 – Eris

+0

謝謝!我不知道這個工具。我會看看 –

回答

0

我有simlair的情況,我解決了它與構建配置和條件編譯。這不是最好的解決方案,但它爲我工作並解決了問題。這是發票:

1.創建DataContext的接口

首先,你需要創建將由你要使用這兩個方面CLASSE來實現的接口。讓它被命名爲'IMyDataContext'。在它裏面,你需要描述你需要訪問的所有DbSets。

public interaface IMyDataContext 
{ 
    DbSet<SomeEntity> SomeEntities { get; set; } 
} 

而且兩者你的上下文類需要impelemt它:

public partial class RealDataContext : DataContext, IMyDataContext 
{ 
    DbSet<SomeEntity> SomeEntities { get; set; } 

    /* Contructor, Initialization code, etc... */ 
} 

public class FakeDataContext : DataContext, IMyDataContext 
{ 
    DbSet<SomeEntity> SomeEntities { get; set; } 

    /* Mocking, test doubles, etc... */ 
} 

通過你甚至可以使性的判定只讀在接口級的方式。

2.新增「測試」構建配置

Here你可以找到如何添加新的生成配置。我給我的配置命名爲'測試'。創建新配置後,請轉到您的DAL項目屬性,左側窗格中的「生成」部分。在'配置'下拉列表中選擇您剛剛創建的配置,並在輸入'條件編譯符號'中輸入'TEST'。

3 Incapsulate背景下注射

需要明確的是,我的做法仍然是法/基於屬性DI液=)

所以現在我們需要實現一些注入代碼。爲了簡單起見,如果您需要更多的抽象,您可以將其直接添加到您的服務中或提取到另一個類中。主要想法是使用條件編譯方向而不是IoC框架。

public class Service 
{ 
    // Injector method 
    private IMyDataContext GetContext() { 
     // Here is the main code 

#if TEST // <-- In 'Test' configuration 
      // we will use fake context 
      return new FakeDataContext(); 
#else 
      // in any other case 
      // we will use real context 
      return new RealDataContext(); 
#endif 

    } 

    public int CountSomeEntites() 
    { 
     // the service works with interface and does know nothing 
     // about the implementation 

     using (IMyDataContext ctx = GetContext()) 
     { 
      int result = ctx.SomeEntities.Count(); 
      return result; 
     } 
    } 
} 

限制

所描述的方法解決了您所描述的問題,但它有一個限制:作爲的IoC允許你切換上下文動態在運行時,條件complation需要你重新編譯解決方案。

在我的情況下,這不是一個問題 - 我的代碼沒有被100%的測試覆蓋,而且我也沒有在每個版本上運行它們。通常我只在提交代碼前運行測試,因此在VS中切換構建配置非常簡單,運行測試,確保沒有任何內容被破壞,然後返回到調試模式。在發佈模式下,您也不需要運行測試。即使您需要 - 您可以製作「發佈構建測試模式」配置並繼續使用相同的解決方案。

另一個問題是,如果您持續集成 - 您需要對生成服務器進行其他設置。這裏有兩種方法:

  • 設置兩個構建定義:一個用於發佈,一個用於測試。如果您的服務器設置爲自動釋放,則需要小心,因爲在部署第一臺服務器時,會在第二臺服務器中顯示測試失敗。
  • 設置複雜構建定義,它首次在Test配置中構建您的代碼,運行測試並且如果它們正常 - 然後重新編譯目標配置中的代碼並準備部署。

因此,任何解決方案都是簡化性和靈活性之間的又一折衷。

UPDATE

一段時間後,我明白我上面描述的方法是非常沉重的。我的意思是 - 建立配置。如果只有兩個IDataContext的實現:'Core'和'Fake',你可以簡單地使用bool參數和簡單的if/else分支來代替編譯指令#if/#else/#endif以及配置你的構建服務器的所有頭痛。

如果你有兩個以上的實現 - 你可以使用枚舉和switch塊。這裏的一個探討是定義你將在default的情況下返回什麼,或者如果值超出了枚舉的範圍。

但是這種方法的主要好處是您不再需要編譯時間。注射器參數可隨時更改,例如使用web.config和ConfigurationManager。使用它你可以在運行時切換你的數據上下文。