2

我有至少 2 个其他类使用的存储库类。这个存储库类需要初始化——成本很高(查询数据库)。现在,我在需要的地方创建单独的存储库实例。问题是,每次我创建存储库时都必须对其进行初始化。如何将这样的存储库设计为对 TDD 友好?我首先想到的是 Singleton,但这不是解决方案

4

5 回答 5

3

我希望 TDD 友好您的意思是“可测试”代码。对于单例 ObjectX,我认为最常见的方法是将“控制创建”的责任(SRP) 分配给另一个类,以便 ObjectX 完成它应该做的所有事情。

然后你有另一个类 ObjectXFactory 或 Host 或任何你想调用的类,它负责为所有客户端提供单个实例(并在需要时提供线程同步等)

  • 对象 X 可以独立进行 TDD。您可以在测试用例和测试功能中创建一个新实例。
  • 另一方面,ObjectXFactory 也很容易测试。您只需要查看多个 GetInstance() 调用是否返回相同的对象。或者更好地将此责任委托给像 Spring 这样的 IOC 框架,它允许您以声明方式标记对象定义以获得单例行为(也为您节省编写测试的工作)

您只需要了解并遵守不应调用 ObjectX 构造函数的团队约定 - 始终使用 ObjectXFactory.CreateInstance()。(如果您发现自己有意识/纪律问题,请将 ObjectX 的 ctor 标记为内部,并且通过偷偷摸摸的InternalsVisibleToAttribute仅对测试程序集可见)HTH

于 2009-04-23T07:07:22.880 回答
2

TDD 部分的一个答案是学习模拟。

查看 Stephen Walther 的这篇优秀文章:

http://stephenwalther.com/blog/archive/2008/03/23/tdd-introduction-to-rhino-mocks.aspx

于 2009-04-23T06:26:14.553 回答
1

您使用任何类型的 IOC 容器吗?Unity是我选择的容器,它包含一个ContainerControledLifetimeManager,它使您的类成为单例,但不由您自己管理。

于 2009-04-23T06:25:40.607 回答
1

在考虑单例之前,请考虑缓存实例以提高性能。但是对于 TDD 友好的设计,请考虑策略注入,以便可以删除“慢”位进行测试并用存根和模拟替换。如果可以的话,尽量不要在测试中进行 db 调用。

于 2009-04-23T06:25:41.857 回答
1

你不能这样做——至少在真正的 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());
    }
}

您想要测试SomeObjectFindAll() 将返回一组预期的结果——这就是 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 的文章有一个更全面的例子和更好的例子。

于 2009-04-23T07:46:08.137 回答