TDD 友好的单例类

发布于 2024-07-18 00:15:37 字数 258 浏览 6 评论 0 原文

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

I have repository class that is used by at least 2 other classes. This repository class needs to be initialized - which is high in cost (querying database). Now, I create separate instances of repository wherever I need it. The thing is, that everytime I create repository it has to be initialized. How to design such repository to be TDD-friendly? The first thing in my mind was Singleton but it's not the solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

辞旧 2024-07-25 00:15:37

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

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

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

您只需要教育并遵守团队约定,即 ObjectX 构造函数是不被调用 - 始终使用 ObjectXFactory.CreateInstance()。 (如果您发现自己有意识/纪律问题,请通过偷偷摸摸的 InternalsVisibleToAttribute)
华泰

I hope by TDD-friendly you mean 'testable' code. For a Singleton ObjectX, I think the most common way is to split the responsibility (SRP) of 'controlling creation' to another class so ObjectX does all the things it is supposed to do.

Then you have another class ObjectXFactory or Host or whatever you wanna call it that is responsible for providing a single instance for all clients (and providing thread sync if needed and so on)

  • Object X can be TDDed independently. You can create a new instance in your test case and test functionality.
  • ObjectXFactory on the other hand is also easy to test.. you just need to see if multiple GetInstance() calls return the same object. OR better delegate this responsibility to an IOC framework like Spring, which lets you declaratively mark an object definition to obtain singleton behavior (Saving you the effort of writing tests as well)

You just need to educate and conform to a Team convention that ObjectX constructor is not to be called - always use ObjectXFactory.CreateInstance(). (If you find that you have a awareness/discipline problem, mark ObjectX's ctor as internal and visible to only to the test assembly via the sneaky InternalsVisibleToAttribute)
HTH

时光无声 2024-07-25 00:15:37

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

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

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

One answer for the TDD part is learn mocking.

Check out this excellent article by Stephen Walther:

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

孤独难免 2024-07-25 00:15:37

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

Do you use any type of IOC container? Unity is my container of choice, and it contains a ContainerControledLifetimeManager which makes your class a singleton, but not managed by yourself.

二货你真萌 2024-07-25 00:15:37

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

Consider caching instances for performance improvement before you consider singletons. But for TDD friendly designs consider strategy injection so that 'slow' bits can be removed for testing and replaced with stubs and mocks. Try not to do db calls in tests if you can.

笑脸一如从前 2024-07-25 00:15:37

你不能那样做——至少在真正的 TDD 意义上不能这样做。

依赖 Unity 等 DI/IoC 策略意味着您的测试依赖于外部组件,而不是孤立进行测试。

然后测试就变成了集成测试,而不是单元测试。

==忽略下面的答案==

我想您想知道如何使存储库可测试。

为它引入一个接口将允许您模拟或存根它,这反过来将确保您可以独立于存储库的任何具体实现来测试您的对象。

我将使用 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 子句。

现在,你有了你的对象:

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 的文章。

You can't do that -- at least not in a true TDD sense.

Relying on DI/IoC strategies such as Unity means your tests are dependent on an external component and are not tested in isolation.

The tests then become integration tests, not unit tests.

==Ignore the answer below here==

I guess you wanted to know how to make Repository testable.

Introducing an interface for it would allow you to mock or stub it, which will in turn make sure that you can test your objects independent of any concrete implementation of Repository.

I'll illustrate this using Rhino Mocks 3.5 for .NET 3.5:

Let's make an interface out of Repository, let's call that IRepository

public interface IRepository
{
}

Now, since you need to use IRepository for two different objects, then let's just use generics so you can instantiate your repository with that:

public interface IRepository<T>

of course that would mean that you would have some sort of find method:

{
    public IEnumerable<T> Find(Criteria criteria)
}

where your criteria object is some object that allows you to set what to look for, e.g., your where clause.

Now, you have your object:

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());
    }
}

You want to to test SomeObject such that FindAll() will return an expected set of results -- this is where Rhino Mocks would come in:

[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)
    }
}

Note that the code above is not TDD best practice in all respects, but it's a place to start.

Key concept here is introducing interfaces to allow for loose coupling of objects, especially when the object tends to do things like access databases, file systems, etc.

There is a more comprehensive example and better examples on Ben Hall's article on Rhino Mocks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文