代码之家  ›  专栏  ›  技术社区  ›  DavidGouge

使用EF4“代码优先”和存储库进行单元测试

  •  49
  • DavidGouge  · 技术社区  · 15 年前

    我正在尝试对一个非常简单的ASP.NET MVC测试应用程序进行单元测试,这是我使用最新的EF4CTP中的代码优先方法构建的。我在单元测试/模拟等方面经验不足。

    这是我的知识库类:

    public class WeightTrackerRepository
    {
        public WeightTrackerRepository()
        {
            _context = new WeightTrackerContext();
        }
    
        public WeightTrackerRepository(IWeightTrackerContext context)
        {
            _context = context;
        }
    
        IWeightTrackerContext _context;
    
        public List<WeightEntry> GetAllWeightEntries()
        {
            return _context.WeightEntries.ToList();
        }
    
        public WeightEntry AddWeightEntry(WeightEntry entry)
        {
            _context.WeightEntries.Add(entry);
            _context.SaveChanges();
            return entry;
        }
    }
    

    这是IweightTrackerContext

    public interface IWeightTrackerContext
    {
        DbSet<WeightEntry> WeightEntries { get; set; }
        int SaveChanges();
    }
    

    …及其实现,WeightTrackerContext

    public class WeightTrackerContext : DbContext, IWeightTrackerContext
    {
        public DbSet<WeightEntry> WeightEntries { get; set; }
    }
    

    在我的测试中,我有以下几点:

    [TestMethod]
    public void Get_All_Weight_Entries_Returns_All_Weight_Entries()
    {
        // Arrange
        WeightTrackerRepository repos = new WeightTrackerRepository(new MockWeightTrackerContext());
    
        // Act
        List<WeightEntry> entries = repos.GetAllWeightEntries();
    
        // Assert
        Assert.AreEqual(5, entries.Count);
    }
    

    我的MockweightTrackerContext:

    class MockWeightTrackerContext : IWeightTrackerContext
    {
        public MockWeightTrackerContext()
        {
            WeightEntries = new DbSet<WeightEntry>();
            WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("01/06/2010"), Id = 1, WeightInGrams = 11200 });
            WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("08/06/2010"), Id = 2, WeightInGrams = 11150 });
            WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("15/06/2010"), Id = 3, WeightInGrams = 11120 });
            WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("22/06/2010"), Id = 4, WeightInGrams = 11100 });
            WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("29/06/2010"), Id = 5, WeightInGrams = 11080 });
        }
    
        public DbSet<WeightEntry> WeightEntries { get;set; }
    
        public int SaveChanges()
        {
            throw new NotImplementedException();
        }
    }
    

    当我试图建立一些测试数据时,出现了我的问题,因为我无法创建 DbSet<> 因为它没有构造函数。我觉得我用我的整个方法来模仿我的上下文是错误的。对于这个完整的单元测试新手来说,任何建议都是最受欢迎的。

    3 回复  |  直到 12 年前
        1
  •  48
  •   Darren Lewis    15 年前

    通过在上下文中的factory方法set()创建一个dbset,但不希望在单元测试中依赖于ef。因此,您需要看到的是使用idbset接口实现dbset的存根,或者使用moq或rinomock等模拟框架之一实现存根。假设您编写了自己的存根,您只需将weightentry对象添加到内部散列集。

    如果您搜索objectset和iobjectset,那么您在单元测试ef方面可能会有更多的运气。这些是在代码第一次CTP发布之前与dbset对应的,从单元测试的角度来看,它们有更多的相关内容。

    这里有一个 excellent article 讨论了ef代码的可测试性。它使用IObjectset,但我认为它仍然相关。

    作为对大卫评论的回应,我在下面添加了这个附录,因为它不适合-评论。不确定这是否是长时间评论回复的最佳实践?

    您应该更改IweghtTrackerContext接口,以从WeightEntries属性返回IDBSet,而不是DBset具体类型。然后,您可以使用模拟框架(推荐)或自己的自定义存根创建模拟上下文。这将从weightentries属性返回stubdbset。

    现在,您还将拥有依赖于IweightTrackerContext的代码(即自定义存储库),在生产环境中,您将在将实现IweightTrackerContext的实体框架weightTrackerContext中传递这些代码。这通常是通过使用IOC框架(如Unity)的构造函数注入来实现的。对于测试依赖于ef的存储库代码,您将在mockcontext实现中通过,因此被测试的代码认为它在与“真正的”ef和数据库对话,并按预期的方式(希望如此)运行。当您消除了对可更改的外部数据库系统和EF的依赖性之后,您就可以在单元测试中可靠地验证存储库调用。

    模拟框架的很大一部分是提供验证对模拟对象的调用以测试行为的能力。在上面的示例中,您的测试实际上只是测试dbset-add功能,这不应该是您所关心的,因为MS将为此进行单元测试。您想知道的是,如果合适的话,对附加数据库集的调用是从您自己的存储库代码中进行的,而这正是模拟框架的切入点。

    对不起,我知道这很难理解,但是如果你通读了那篇文章,就会更清楚,因为斯科特·艾伦比我更擅长解释这件事:)

        2
  •  41
  •   Merritt    15 年前

    根据DAZ所说的(正如我在他的评论中所提到的),您将需要“伪造”IDBSET的实现。可以找到CTP 4的示例 here 但要使其正常工作,您必须自定义find方法,并为以前无效的两个方法(如add)添加返回值。

    以下是我自己为CTP 5制作的示例:

    public class InMemoryDbSet<T> : IDbSet<T> where T : class
    {
        readonly HashSet<T> _data;
        readonly IQueryable _query;
    
        public InMemoryDbSet()
        {
            _data = new HashSet<T>();
            _query = _data.AsQueryable();
        }
    
        public T Add(T entity)
        {
            _data.Add(entity);
            return entity;
        }
    
        public T Attach(T entity)
        {
            _data.Add(entity);
            return entity;
        }
    
        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
        {
            throw new NotImplementedException();
        }
    
        public T Create()
        {
            return Activator.CreateInstance<T>();
        }
    
        public virtual T Find(params object[] keyValues)
        {
            throw new NotImplementedException("Derive from FakeDbSet and override Find");
        }
    
        public System.Collections.ObjectModel.ObservableCollection<T> Local
        {
            get { return new System.Collections.ObjectModel.ObservableCollection<T>(_data); }
        }
    
        public T Remove(T entity)
        {
            _data.Remove(entity);
            return entity;
        }
    
        public IEnumerator<T> GetEnumerator()
        {
            return _data.GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }
    
        public Type ElementType
        {
            get { return _query.ElementType; }
        }
    
        public Expression Expression
        {
            get { return _query.Expression; }
        }
    
        public IQueryProvider Provider
        {
            get { return _query.Provider; }
        }
    }
    
        3
  •  0
  •   Community Mohan Dere    9 年前

    你可能会出错

     AutoFixture was unable to create an instance from System.Data.Entity.DbSet`1[...], most likely because it has no public constructor, is an abstract or non-public type.
    

    dbset有一个受保护的构造函数。

    通常用于支持可测试性的方法创建自己的数据库集实现

    public class MyDbSet<T> : IDbSet<T> where T : class
    {
    

    使用此作为

    public class MyContext : DbContext
    {
        public virtual MyDbSet<Price> Prices { get; set; }
    }
    

    如果您查看下面的问题2ns答案,您应该能够找到自定义dbset的完整实现。

    Unit testing with EF4 "Code First" and Repository

    然后

     var priceService = fixture.Create<PriceService>();
    

    不应引发异常。