我有至少 2 个其他类使用的存储库类。这个存储库类需要初始化——成本很高(查询数据库)。现在,我在需要的地方创建单独的存储库实例。问题是,每次我创建存储库时都必须对其进行初始化。如何将这样的存储库设计为对 TDD 友好?我首先想到的是 Singleton,但这不是解决方案。
5 回答
我希望 TDD 友好您的意思是“可测试”代码。对于单例 ObjectX,我认为最常见的方法是将“控制创建”的责任(SRP) 分配给另一个类,以便 ObjectX 完成它应该做的所有事情。
然后你有另一个类 ObjectXFactory 或 Host 或任何你想调用的类,它负责为所有客户端提供单个实例(并在需要时提供线程同步等)
- 对象 X 可以独立进行 TDD。您可以在测试用例和测试功能中创建一个新实例。
- 另一方面,ObjectXFactory 也很容易测试。您只需要查看多个 GetInstance() 调用是否返回相同的对象。或者更好地将此责任委托给像 Spring 这样的 IOC 框架,它允许您以声明方式标记对象定义以获得单例行为(也为您节省编写测试的工作)
您只需要了解并遵守不应调用 ObjectX 构造函数的团队约定 - 始终使用 ObjectXFactory.CreateInstance()。(如果您发现自己有意识/纪律问题,请将 ObjectX 的 ctor 标记为内部,并且通过偷偷摸摸的InternalsVisibleToAttribute仅对测试程序集可见)HTH
TDD 部分的一个答案是学习模拟。
查看 Stephen Walther 的这篇优秀文章:
http://stephenwalther.com/blog/archive/2008/03/23/tdd-introduction-to-rhino-mocks.aspx
您使用任何类型的 IOC 容器吗?Unity是我选择的容器,它包含一个ContainerControledLifetimeManager,它使您的类成为单例,但不由您自己管理。
在考虑单例之前,请考虑缓存实例以提高性能。但是对于 TDD 友好的设计,请考虑策略注入,以便可以删除“慢”位进行测试并用存根和模拟替换。如果可以的话,尽量不要在测试中进行 db 调用。
你不能这样做——至少在真正的 TDD 意义上不能。
依赖像 Unity 这样的 DI/IoC 策略意味着您的测试依赖于外部组件,而不是孤立地进行测试。
然后测试变成集成测试,而不是单元测试。
==这里忽略下面的答案==
我猜你想知道如何使存储库可测试。
为它引入一个接口将允许您模拟或存根它,这反过来将确保您可以独立于任何具体的 Repository 实现来测试您的对象。
我将使用Rhino Mocks 3.5 for .NET 3.5来说明这一点:
让我们从 Repository 中创建一个接口,我们称之为IRepository
public interface IRepository
{
}
现在,由于您需要对两个不同的对象使用 IRepository,那么让我们只使用泛型,这样您就可以使用它来实例化您的存储库:
public interface IRepository<T>
当然这意味着你会有某种 find 方法:
{
public IEnumerable<T> Find(Criteria criteria)
}
where 您的条件对象是一些允许您设置要查找的对象的对象,例如,您的 where 子句。
现在,你有你的对象:
public class SomeObject
{
IRepository<SomeObject> repository;
public SomeObject(){}
public IRepository<SomeObject> repository { get; set; }
IEnumerable<SomeObject> FindAll()
{
//let's assume new Criteria() will return all results
return respository.Find(new Criteria());
}
}
您想要测试SomeObject
FindAll() 将返回一组预期的结果——这就是 Rhino Mocks 的用武之地:
[TestFixture]
public class SomeObjectTests
{
[Test]
public void TestSomeObjectFindAll()
{
IRepository<SomeObject> stubRepository = MockRepsitory.GenerateStub<IRepsitory<SomeObject>>();
stubRepository.Expect(r => r.Find(new Criteria())
.Return(new List<SomeObject>{
new SomeObject(),
new SomeObject(),
new SomeObject());
var testObject = new SomeObject { Repository = stubRepository };
IList<SomeObject> findAllResult = testObject.FindAll();
//returned list contains 3 elements, as expected above
Assert.AreEqual(3, findAllResult.Count)
}
}
请注意,上面的代码并不是所有方面的 TDD 最佳实践,但它是一个起点。
这里的关键概念是引入接口以允许对象的松散耦合,特别是当对象倾向于执行访问数据库、文件系统等操作时。
Ben Hall 关于 Rhino Mocks 的文章有一个更全面的例子和更好的例子。