【问题标题】:Unit testing first, Database Count using LINQ首先进行单元测试,使用 LINQ 进行数据库计数
【发布时间】:2016-08-23 16:17:41
【问题描述】:

由于我现在正在练习单元测试第一个开发过程,所以我在进行单元测试时遇到了这个问题。

我必须创建一个测试来验证或断言它将在数据库的特定表中计数的项目。还有,它有参数。

  [TestMethod]
  public void verify_items_count()
  {
      //Arrange
      Mock<IRepository> Repo = new Mock<IRepository>();
      Repo.Setup(t => t.Query().Count(r => r.Id == "**WHAT SHOULD I PUT HERE IF THIS IS ONLY A MOCK**")).Returns(12);

      //Act
      //Assert
  }

如果我断言它检查它是否返回 12 也有它的参数似乎是没有意义的,因为我们都知道它不会在数据库中调用。不过,我已将其设置为 12。

请先帮我写一个单元测试。 我在想它是否应该在集成测试中。 但我读过一切都应该从单元测试开始。所以我相信有办法。

【问题讨论】:

  • 你实际测试的是哪个类?看起来你正在测试 Mock..
  • 我真的不明白。我必须计算数据库中的项目。但是因为我不必连接数据库。我必须嘲笑它​​。在实际代码中,我可能会使用这种 LINQ 查询,这就是我设置它的原因。
  • 所以计数代码是你自己写的?你应该只测试你自己的代码..
  • 可能想要模拟存储库使用的数据库上下文。看看:stackoverflow.com/a/36862759/592182

标签: c# unit-testing mocking tdd moq


【解决方案1】:

一个天真的第一个测试可能是

  [TestMethod]
  public void verify_items_count()
  {
      //Arrange
      var expectedCount = 3;
      var repository = MakeRepositoryWithElements(expectedCount);

      //Act
      var actualCount = repository.Count();

      //Assert
      Assert.AreEqual(expectedCount, actualCount);
  }

在幕后,MakeRepositoryWithElements(n) 必须设法

  • 实例化一个真实存储库。不是模拟或存根,因为您想测试 Count() 的真实实现。这就是测试的全部内容。

  • 将 n 个元素放入存储库中。

教科书式的冗长方法可能是:

  1. 直接将MakeRepositoryWithElements()Count() 实现为针对真实数据库的SQL 查询。

  2. 测试通过

  3. 在 TDD 周期的重构步骤中,意识到:

    • 如果您想重复使用同一个数据库作为连续测试的数据源,则针对真实数据库的测试速度很慢,并且需要进行大量数据清理。

    • 您的平台已经在 SQL 之上提供了一个抽象,可以为您完成繁重的查询并公开对象集合而不是数据库表。 IE。某种DatabaseContext。方便的是,数据库上下文有一个接口(或者你围绕它创建一个),你可以实现它来制作你自己的快速内存版本。在测试中,您将存储库连接到该版本,而不是完整的 DatabaseContext

因此,您将进行重构,直到每个对象都严格遵守单一职责原则 - 存储库仅说明对象集合,而 DatabaseContext 涉及底层持久性技术 - 并且测试在内存中快速运行。类似的东西

public class WhateverRepository
{
  private IDatabaseContext _dbContext;

  public WhateverRepository(IDatabaseContext databaseContext) 
  {  
    _dbContext = databaseContext;
  }

  public int Count()
  {
    // This is possible because the context exposes collections of objects to us
    return _dbContext.Whatevers.Count(); 
  }
}

public WhateverRepository MakeRepositoryWithElements(int elementCount)
{
  var context = new InMemoryDatabaseContext();
  for (int i = 0; i < elementCount; i++)
  {
    context.Whatevers.Add(new Whatever());
  }
  return new WhateverRepository(context);
}

如果您是一位经验丰富的开发人员或已经在您的系统中实现了这样的存储库,您可能会直接跳到优化的解决方案。

最后,请注意 Count() 是一个微不足道的单线,如果数据已经以对象集合的形式暴露给您,则不涉及任何逻辑,因为平台免费为您提供 Count()。因此,你可能想完全跳过这个测试,除非你仍然想通过它的动作来练习一些 TDD,或者你想把这个测试作为一个“开始”来帮助你存储库的第一个解耦实现和一些测试辅助方法。

【讨论】:

    【解决方案2】:

    不要模拟您的存储库。而是创建一个 DummyRepository 来实现您的 IRepository ,例如如果你有这样的IRepository 结构

    public interface IRepository
    {
        void Add<T>( T entity ) where T : class;
        T Get<T>( int id, params Expression<Func<T, object>>[] includes ) where T : BaseEntity;
        IQueryable<T> GetAll<T>( params Expression<Func<T, object>>[] includes ) where T : class;
        void Remove<T>( T entity ) where T : class;
        void SaveChanges();
    }
    

    那么你的 DummyRepository 应该这样做

    public class DummyRepository : IRepository
    {
        private readonly Dictionary<Type, Object> _database;
    
        public DummyRepository()
        {
            _database = new Dictionary<Type, object>();
        }
    
        public void Add<T>( T entity ) where T : class
        {
            var table = GetEntityTable<T>();
    
            var entityBase = entity as BaseEntity;
            if ( entityBase == null )
                throw new NotImplementedException( "You must inherit from entity base" );
    
            if ( entityBase.Id == 0 )
            {
                var idToUse = table.Any() ? table.Max( x => ( x as BaseEntity ).Id ) + 1 : 1;
                typeof( BaseEntity ).GetProperty( "Id" ).SetValue( entityBase, idToUse );
                table.Add( entity );
            }
            else
            {
                var itemInList = table.FirstOrDefault( x => ( x as BaseEntity ).Id == entityBase.Id );
                if ( itemInList == null )
                    table.Add( entity );
                else
                {
                    var itemIndex = table.IndexOf( itemInList );
                    table[ itemIndex ] = entity;
                }
            }
        }
    
    
        private List<T> GetEntityTable<T>() where T : class
        {
            List<T> table;
            if ( _database.ContainsKey( typeof( T ) ) )
                table = (List<T>)_database[ typeof( T ) ];
            else
            {
                table = new List<T>();
                _database.Add( typeof( T ), table );
            }
            return table;
        }
    
        public T Get<T>( int id, params Expression<Func<T, object>>[] includes ) where T : BaseEntity
        {
            if ( !_database.ContainsKey( typeof( T ) ) )
                return null;
            return ( (List<T>)_database[ typeof( T ) ] ).FirstOrDefault( x => x.Id == id );
        }
    
        public IQueryable<T> GetAll<T>( params Expression<Func<T, object>>[] includes ) where T : class
        {
            if ( _database.ContainsKey( typeof( T ) ) )
                return ( (List<T>)_database[ typeof( T ) ] ).AsQueryable();
            return new List<T>().AsQueryable();
        }
    
        public void Remove<T>( T entity ) where T : class
        {
            if ( _database.ContainsKey( entity.GetType() ) )
                ( (IList)_database[ entity.GetType() ] ).Remove( entity );
        }
    
        public void SaveChanges() { }
    }
    

    顺便说一下,你需要一个 BaseEntity 来让这个例子工作,就像这样

    public class BaseEntity
    {
        public int Id { get; set; }
    }
    

    所以在您的测试开始时,您需要 Add 数据,希望您能够编写可以检查您的查询的测试。

    下面的代码不会出现,因为您没有 Service 类,但我想在这里给您一个示例,说明如何使用它。

    IRepository repository = new DummyRepository();
    repository.Add(new BaseEntity { Id = 1 } );
    var service = new Service(repository);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-01
      • 1970-01-01
      • 2011-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-15
      • 1970-01-01
      相关资源
      最近更新 更多