如何使用结构图模拟基础 class?
How to mock a base class using structure map?
我正在使用 NHibernate 并且有很多存储库,它们都继承自基础 NHibernateRepository class。这是我的存储库之一:
public class StaffRepository : NHibernateRepository<IStaff>,
{
public IEnumerable<IStaff> GetBySiteRegionAndMonth(int siteId, int regionId, DateTime firstOfMonth)
{
return Repository.Where(ab => ab.SiteId == siteId && ab.WorkDate >= firstOfMonth && ab.WorkDate < firstOfMonth.AddMonths(1));
}
}
和基础class:
public class NHibernateRepository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
protected ISession session;
public NHibernateRepository()
{
this.session = new SessionCache().GetSession();
}
public IQueryable<TEntity> Query
{
get
{
return session.Query<TEntity>();
}
}
// Add
public void Add(TEntity entity)
{
session.Save(entity);
}
// GetById
public TEntity GetById(int id)
{
// return session.Load<TEntity>(id);
return this.Query.SingleOrDefault(e => e.Id == id);
}
}
我现在正尝试使用不会访问真实数据库但会使用静态列表的测试 class 模拟基础 class NHibernateRepository
。这是我在结构图容器中注册的测试 class:
x.For(typeof(IRepository<>)).Use(typeof(TestNHibernateRepository<>));
我的问题是,真正的 NHibernateRepository
仍在测试中使用。根据我的注册,我正在使用真实的 StaffRepository
:
x.For<IStaffRepository>().Singleton().Use<StaffRepository>();
我的所有其他测试 classes 都注入良好,但我认为这是有问题的,因为它是继承的 class。
如何确保我的 StaffRepository 使用 TestNHibernateRepository
而不是 NHibernateRepository
?
虽然创建伪造的实现很容易,但您的单元测试将非常不可靠,因为您的存储库公开了 IQueryable<T>
并导致 tight coupling 并且它总是会导致特定的实现漏过。
这意味着如果您在单元测试中使用 IQueryable<T>
上的 LINQ to Objects 实现,几乎所有在 IQueryable<T>
上编写的 LINQ 查询都将始终成功,但它们很可能会失败使用 NHibernate 查询提供程序时。
相反,您应该使用集成测试来测试依赖于 IRepository<T>
的 类,这意味着您与 真实 数据库通信,而不是内部-记忆替身。单元测试应该在不同的级别进行。
尽管 IQueryable<T>
是一个接口,但它并不是真正的抽象,或者至少,它是一个 Leaky Abstraction; a Dependency Inversion Principle violation。因此,您应该确保 IQueryable<T>
仅在您的数据访问层中使用。
我发现一个非常有效的解决方案是使用 query handlers,其中查询对象(数据)是核心层的一部分,而它们的处理程序(使用 [= IQueryable<T>
) 形式的 32=] 是数据访问层的一部分。您集成测试这些处理程序,而这些处理程序的消费者可以再次进行单元测试。
我正在使用 NHibernate 并且有很多存储库,它们都继承自基础 NHibernateRepository class。这是我的存储库之一:
public class StaffRepository : NHibernateRepository<IStaff>,
{
public IEnumerable<IStaff> GetBySiteRegionAndMonth(int siteId, int regionId, DateTime firstOfMonth)
{
return Repository.Where(ab => ab.SiteId == siteId && ab.WorkDate >= firstOfMonth && ab.WorkDate < firstOfMonth.AddMonths(1));
}
}
和基础class:
public class NHibernateRepository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
protected ISession session;
public NHibernateRepository()
{
this.session = new SessionCache().GetSession();
}
public IQueryable<TEntity> Query
{
get
{
return session.Query<TEntity>();
}
}
// Add
public void Add(TEntity entity)
{
session.Save(entity);
}
// GetById
public TEntity GetById(int id)
{
// return session.Load<TEntity>(id);
return this.Query.SingleOrDefault(e => e.Id == id);
}
}
我现在正尝试使用不会访问真实数据库但会使用静态列表的测试 class 模拟基础 class NHibernateRepository
。这是我在结构图容器中注册的测试 class:
x.For(typeof(IRepository<>)).Use(typeof(TestNHibernateRepository<>));
我的问题是,真正的 NHibernateRepository
仍在测试中使用。根据我的注册,我正在使用真实的 StaffRepository
:
x.For<IStaffRepository>().Singleton().Use<StaffRepository>();
我的所有其他测试 classes 都注入良好,但我认为这是有问题的,因为它是继承的 class。
如何确保我的 StaffRepository 使用 TestNHibernateRepository
而不是 NHibernateRepository
?
虽然创建伪造的实现很容易,但您的单元测试将非常不可靠,因为您的存储库公开了 IQueryable<T>
并导致 tight coupling 并且它总是会导致特定的实现漏过。
这意味着如果您在单元测试中使用 IQueryable<T>
上的 LINQ to Objects 实现,几乎所有在 IQueryable<T>
上编写的 LINQ 查询都将始终成功,但它们很可能会失败使用 NHibernate 查询提供程序时。
相反,您应该使用集成测试来测试依赖于 IRepository<T>
的 类,这意味着您与 真实 数据库通信,而不是内部-记忆替身。单元测试应该在不同的级别进行。
尽管 IQueryable<T>
是一个接口,但它并不是真正的抽象,或者至少,它是一个 Leaky Abstraction; a Dependency Inversion Principle violation。因此,您应该确保 IQueryable<T>
仅在您的数据访问层中使用。
我发现一个非常有效的解决方案是使用 query handlers,其中查询对象(数据)是核心层的一部分,而它们的处理程序(使用 [= IQueryable<T>
) 形式的 32=] 是数据访问层的一部分。您集成测试这些处理程序,而这些处理程序的消费者可以再次进行单元测试。