0

我已经开始构建一个构建器,以便我可以轻松地为我正在编写的单元测试创​​建测试数据。

构建器的基本结构是:

public class MyClassBuilder
{
    public int id = 0; //setting the default value here

    //allows MyClass to be built with a specific id
    public MyClassBuilder WithId(int id)
    {
        this.id = id;
        return this;
    }

    public MyClass Build()
    {
        return new MyClass(id);
    }
}

那么这个模式的用法就变成了:

MyClass mc = new MyClassBuilder().WithId(5).Build();

我对此很满意......但我有疑问的是什么时候MyClass有一个非平凡的属性......我有点不确定如何用默认值构造它。

public class MyClassBuilder
{
    public int id = 0; //setting the default value here

    //do I construct the default value using a MySecondClassBuilder?
    public MySecondClass mySecondClass;

    //allows MyClass to be built with a specific id
    public MyClassBuilder WithId(int id)
    {
        this.id = id;
        return this;
    }

    public MyClassBuilder WithMySecondClass(MySecondClass mySecondClass)
    {
        this.mySecondClass = mySecondClass;
    }

    public MyClass Build()
    {
        return new MyClass(id);
    }
}

我的假设是我会创建一个构建器MySecondClass并使用它来创建默认实现。

谁能确认我的假设是正确的并且是最佳实践?

我目前正在测试我的假设,但我想我会在 StackOverflow 上记录这个想法,因为我可以使用 google 找到的构建器模式的唯一示例只构建了值类型而不是引用类型的属性。

4

2 回答 2

1

与大多数事情一样 - 这取决于。

如果你想在 mySecondClass 字段中引用语义(即:你想保存对另一个类的引用),那么它就像你的整数一样。将默认值设为 null 是完全可以接受的。

但是,如果您想始终保证一个值,则需要为您的第二类构建或仅构建一个新对象。

此外,如果您想要克隆而不是通过引用副本,则需要构造一个新对象并将其分配给引用。

话虽如此,在许多情况下,传统的构造函数比尝试做一个流畅的构建风格的界面更合适。像这样构建一个流畅的界面真的很容易“搞砸”,因为你比传统的构造函数更依赖用户。

例如,在您的代码中,如果用户从不调用 Build 会发生什么?它仍然返回一些东西(因为每个方法都返回一个对象),但它有效吗?作为你们班的消费者,我不确定。另一方面,具有重载的传统构造函数准确地告诉我什么是有效的,什么是无效的,并且是编译时强制执行的。

于 2009-09-10T01:46:06.920 回答
0

一旦我有一个复杂的对象,我希望在单元测试中使用一个虚拟对象,我通常会转向一个模拟/伪造/隔离框架。在 C# 中,我的首选是 Moq,但也有几个,比如 Rhino Mocks、Typemock 等等。

这样做的好处是,它们通常能够使用默认值和空对象来存根存在于原始类型中的所有属性,而您无需执行任何操作,只需一行语句即可创建假对象。同时,您可以轻松地配置对象以返回您想要的那些对您的测试很重要的属性的值。此外,大多数此类框架都支持递归伪造,因此您的类型中的复杂对象也会被伪造,等等。

有人可能不想使用单独的框架是有原因的,在这种情况下,我认为 Reed Copsey 的建议将是手动方式。

如果没有任何理由避免添加模拟框架(这只是对您的测试项目的依赖,假设您已经拆分测试),我会衷心推荐它用于复杂类型。

于 2010-03-01T13:18:28.363 回答