我已经开始构建一个构建器,以便我可以轻松地为我正在编写的单元测试创建测试数据。
构建器的基本结构是:
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找到的构建器模式的唯一示例只构建了属性值,而不是值类型参考类型。
答案 0 :(得分:1)
与大多数事情一样 - 这取决于。
如果你想在mySecondClass字段中引用语义(即:你想保存对另一个类的引用),那么它就像你的整数一样。将默认值设为null可能是完全可以接受的。
但是,如果你想总是保证一个值,你需要为你的第二个类构建或构建一个新对象。
此外,如果您想要克隆而不是通过引用副本,则需要构造一个新对象并将其分配给引用。
话虽如此,在许多情况下,传统的构造函数比尝试使用流畅的构建式界面更合适。很容易“搞乱”一个流畅的界面来构建这样的,因为你比传统的构造函数更依赖于用户。
例如,在您的代码中,如果用户从不调用Build会发生什么?它仍然返回一些东西(因为每个方法返回一个对象),但它是否有效?我不确定你班上的消费者。另一方面,具有重载的传统构造函数向我显示了什么是有效的以及什么是无效的,并且是强制执行编译时的。
答案 1 :(得分:0)
一旦我有一个复杂的对象,我希望在单元测试中使用一个虚拟对象,我通常会转向一个模拟/伪造/隔离框架。在C#中,我的偏好是Moq,但有几个,如Rhino Mocks,Typemock等。
这样做的好处是,它们通常能够使用默认值和空对象来删除原始类型中存在的所有属性,而不必执行除单行语句之外的任何操作来创建伪对象。同时,您可以轻松配置对象,以便为测试中最重要的属性返回所需的值。此外,大多数此类框架都会以递归方式支持伪造,因此您的类型中的复杂对象也会被伪造,等等。
有理由说人们可能不想使用单独的框架,在这种情况下,我认为Reed Copsey的建议将是手动方式。
如果没有任何理由可以避免添加模拟框架(这只会依赖于您的测试项目,假设您已将测试分开),我会衷心地推荐它用于复杂类型。