2011-09-02 49 views
6

我決定開始在我們的應用程序中編寫單元測試。它使用實體框架和存儲庫模式。單元測試EF存儲庫模式與Moq

現在我想開始測試使用存儲庫的邏輯類。我在這裏提供一個簡單的例子。

public class GenericRepository : IRepository 
{ 
    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     var entityName = GetEntityName<TEntity>(); 
     return Context.CreateQuery<TEntity>(entityName); 
    } 
    private string GetEntityName<TEntity>() where TEntity : class 
    { 
     return typeof(TEntity).Name; 
    } 
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

一個簡單的邏輯類從日曆表中降序排列返回不同年(是的,我知道這個詞的日曆在我們的代碼拼寫錯誤):

我在班上GenericRepository三種方法

public class GetDistinctYearsFromCalendar 
{ 
    private readonly IRepository _repository; 

    public GetDistinctYearsFromCalendar() 
    { 
     _repository = new GenericRepository(); 
    } 

    internal GetDistinctYearsFromCalendar(IRepository repository) 
    { 
     _repository = repository; 
    } 

    public int[] Get() 
    { 
     return _repository.Find<Calender_Tbl>(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray(); 
    } 
} 

這是我的第一個測試:

[TestFixture] 
public class GetDistinctYearsFromCalendarTest 
{ 
    [Test] 
    public void ReturnsDistinctDatesInCorrectOrder() 
    { 
     var repositoryMock = new Mock<IRepository>(); 

     repositoryMock.Setup(r => r.Find<Calender_Tbl>(c => c.Year.HasValue)).Returns(new List<Calender_Tbl> 
     { 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2010, 1, 1), 
        Year = 2010 
       }, 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2010, 2, 1), 
        Year = 2010 
       }, 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2011, 1, 1), 
        Year = 2011 
       } 
     }.AsQueryable()); 

     var getDistinct = new GetDistinctYearsFromCalendar(repositoryMock.Object).Get(); 

     Assert.AreEqual(2, getDistinct.Count(), "Returns more years than distinct."); 
     Assert.AreEqual(2011, getDistinct[0], "Incorrect order, latest years not first."); 
     Assert.AreEqual(2010, getDistinct[1], "Wrong year."); 


    } 
} 

這工作正常。但這實際上並不是我想要做的。由於我必須在模擬對象上設置方法Find,所以我還需要知道它是如何在我的邏輯類中調用的。如果我想做TDD,我不想介意這個。我想知道的是我的存儲庫應該提供哪些日曆實體。我想設置GetQuery方法。 像這樣:

repositoryMock.Setup(r => r.GetQuery<Calender_Tbl>()).Returns(new List<Calender_Tbl> 
{ 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2010, 1, 1), 
      Year = 2010 
     }, 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2010, 2, 1), 
      Year = 2010 
     }, 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2011, 1, 1), 
      Year = 2011 
     } 
}.AsQueryable()); 

所以,當發現是在GenericRepository類內部調用GetQuery應該得到正確的日曆實體,我在安裝GetQuery。但這當然不起作用。由於我沒有設置我的模擬對象的查找方法,我沒有得到任何實體。

那該怎麼辦?當然,我可以使用鼴鼠或其他一些模擬一切的框架,但我不想那樣做。在課堂設計或測試中解決問題有什麼我可以做的嗎?

如果我不得不使用當前的解決方案,那麼世界並不是世界末日,但如果財產年度變成不可空的整數?那麼當然,我將不得不在邏輯類中更改我的實現,但我也必須更改測試。我想盡量避免這種情況。

+1

我認爲這樣做是有「GetDistinctYearsFromCalendar」依賴於「IRepository」的一種方式。你可以模擬出「GenericRepository」的測試這樣「GetDistinctYearsFromCalendar」隔離。有關更多詳細信息,請參閱http://en.wikipedia.org/wiki/Dependency_injection。 – Michael

+0

好吧,我沒有看到內部ctor。你在同一個項目中有測試和生產代碼嗎?我認爲正確的做法是完成依賴注入並跳過默認的ctor。 – Michael

+0

我知道我可以只寫我的邏輯爲: 返回_repository.GetQuery (),其中(C => c.Year.HasValue)。選擇(C => c.Year.Value).Distinct()。 OrderBy(c => c).Reverse()。ToArray(); 沒有我一直測試到另一個項目,但暴露的內部用匯編指令的測試項目。 但是,如果我從來不使用另一種類型比GenericRepository呢?那麼是否需要一直進行依賴注入? 這似乎是最好的解決方案嗎?我是盲目的,想要使用存儲庫的所有功能。 – John

回答

12

我可以看到兩種方式:

public class MockRepository : IRepository 
{ 
    private List<object> entities; 
    public MockRepository(params object[] entitites) 
    { 
     this.entities = entities.ToList(); 
    } 

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     return this.entities.OfType<TEntity>().AsQueryable(); 
    } 

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

這是最簡單的,我的首選方式。 Moq不是一切的錘子;)

或者,如果你真的堅持使用Moq(我很受寵,但在這種情況下非常沒必要,因爲你可以對返回的實體進行基於狀態的測試) ,你可以這樣做:

public class GenericRepository : IRepository 
{ 
    public virtual IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     var entityName = GetEntityName<TEntity>(); 
     return Context.CreateQuery<TEntity>(entityName); 
    } 
    private string GetEntityName<TEntity>() where TEntity : class 
    { 
     return typeof(TEntity).Name; 
    } 
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

然後用最小起訂量來覆蓋GetQuery的行爲:

var repository = new Mock<GenericRepository> { CallBase = true }; 

repository.Setup(x => x.GetQuery<Foo>()).Returns(theFoos.AsQueryable()); 

會發生什麼事是查找方法會在GenericRepository類執行,這將反過來GetQuery已被Moq覆蓋提供de固定的一組實體。

我明確設置了CallBase = true,以防萬一您偶然也會發現Find virtual,以便確保始終調用它。如果Find不是虛擬的,那麼在技術上不需要,因爲它總是會在模擬從繼承/嘲諷的實際類中調用。

我會選擇第一個選項,更容易理解發生了什麼,它可以在單個特定測試的上下文之外重複使用(只需傳遞任何需要的實體,它將適用於所有內容)。

+0

因爲我知道嘲笑返回IQueryable的方法是不可測試的,因爲你必須運行數據庫的集成測試我不知道是否有可能嘲笑方法查找並讓它返回我的實體,無論我傳遞什麼參數?我嘗試過It.IsAny <>,但它沒有奏效。 – John

0

最近,一個名爲Effort的新工具出現在EF 6+上,我發現它對於針對假DB的單元測試非常有幫助。請參閱http://effort.codeplex.com/wikipage?title=Tutorials&referringTitle=Home

通過使用這個包管理器控制檯命令添加:

PM> Install-Package Effort.EF6 

然後添加一個界面爲你的DbContext,比方說,如果你使用的是AdventureWorks數據庫(見https://sql2012kitdb.codeplex.com/):

然後更新您的的DbContext添加兩個新參數的構造函數:

/// 
    /// Create a new context based on database name or connection string. 
    /// 
    /// Database name or connection string 
    public AdventureWorksEntities(string nameOrConnectionString) 
     : base(nameOrConnectionString) 
    { 
     this.Configuration.LazyLoadingEnabled = false; 
    } 

    public AdventureWorksEntities(DbConnection connection) 
     : base(connection, true) 
    { 
     this.Configuration.LazyLoadingEnabled = false; 
    } 

添加一個構造函數的接口,你的資料庫:

private IAdventureWorksDbContext _dbContext; 

    public ProductRepository(IAdventureWorksDbContext dbContext) 
    { 
     dbContext.Configuration.AutoDetectChangesEnabled = false; 
     this._dbContext = dbContext; 
    } 

然後將接口添加到您的單元測試的項目和關聯類:

public interface ITestDatabase : IDisposable 
{ 
    IAdventureWorksDbContext CreateContext(); 

    void Dispose(IAdventureWorksDbContext context); 
} 

一些假的數據添加到您的單元測試項目:

public class ProductsTestData 
{ 
    public static void AddTestData(IAdventureWorksDbContext dbContext) 
    { 
     dbContext.Products.Add(new Product() { Id = new Guid("23ab9e4e-138a-4223-bb42-1dd176d8583cB"), Name = "Product A", CreatedDate = DateTime.Now, Description = "Product description..." }); 
     dbContext.Products.Add(new Product() { Id = new Guid("97e1835f-4c1b-4b87-a514-4a17c019df00"), Name = "Product B", CreatedDate = DateTime.Now }); 
     dbContext.SaveChanges(); 
    } 
} 

現在設置你的單元測試類:

[TestClass] 
public class ProductsTest 
{ 
    private ITestDatabase _testDatabaseStrategy; 
    private ProductRepository _productRepository; 
    private IAdventureWorksDbContext _context; 

    [TestInitialize] 
    public void SetupTest() 
    { 
     // create the test strategy. This will initialise a new database 
     _testDatabaseStrategy = CreateTestStrategy(); 

     // add test data to the database instance 
     using (_context = _testDatabaseStrategy.CreateContext()) 
     { 
      ProductsTestData.AddTestData(_context); 
      _context.SaveChanges(); 
     } 

     // initialise the repository we are testing 
     _context = _testDatabaseStrategy.CreateContext(); 
     _productRepository = new ProductRepository(_context); 
    } 

    protected ITestDatabase CreateTestStrategy() 
    { 
     return new EffortDatabaseStrategy(); 
    } 

    [TestCleanup] 
    public void CleanupTest() 
    { 
     // dispose of the database and connection 
     _testDatabaseStrategy.Dispose(_context); 
     _context = null; 
    } 

    [TestMethod] 
    public void GetProductsByTagName() 
    { 
     IEnumerable<Product> products = _productRepository.GetProductsByTagName("Tag 1", false); 
     Assert.AreEqual(1, products.Count()); 
    } 

Wh ERE EffortDatabaseStrategy是:

public class EffortDatabaseStrategy : ITestDatabase 
{ 
    public EffortDatabaseStrategy() 
    { 
    } 

    private DbConnection _connection; 

    public IAdventureWorksDbContext CreateContext() 
    { 
     if (_connection == null) 
     { 
      _connection = Effort.DbConnectionFactory.CreateTransient(); 
     } 
     var context = new AdventureWorksDbContext(_connection); 

     return context; 
    } 

    public void Dispose(IAdventureWorksDbContext context) 
    { 
     if (context != null) 
     { 
      context.Dispose(); 
     } 
    } 

    public void Dispose() 
    { 
    } 
} 

有關詳情,請參閱http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort?msg=5122027#xx5122027xx