我已经编写了一个类的构造函数,我正在测试每个参数是否为null。见下面的例子:
public MyClass(IObjectA objA, IObjectB objB) : IMyClass
{
if (objA == null)
{
throw new ArgumentNullException("objA");
}
if (objB == null)
{
throw new ArgumentNullException("objB");
}
...
}
通常我通过模拟IObjectA
和IObjectB
并将它们传入来进行单元测试(使用Moq)。上面的示例将创建2个单元测试来测试每个场景。
我遇到的问题是第三个参数传递给构造函数。它要求我改变以前的测试,因为我突然得到一个"没有MyClass的构造函数有2个参数"输入异常。
我也使用AutoMockContainer。基本上我希望能够通过在容器中注册一个null对象来测试构造函数。例如:
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void ConstructionThrowsExceptionForNullObjA()
{
// Arrange.
var container = new AutoMockContainer(new MockRepository(MockBehavior.Default));
container.Register<IObjectA>(null);
// Act.
var sut = container.Create<MyClass>();
}
然后,向构造函数添加了多少新参数并不重要。我不需要更新我的单元测试。
遗憾的是,上述单元测试通过了。但出于错误的原因。Register<T>()
方法会引发ArgumentNullException
而不是“&#39;法案”中执行的代码。部分。
有没有人建议能够测试构造函数参数,而不必在以后添加新参数时重新访问单元测试?
答案 0 :(得分:2)
通过使用工厂模式或构建器模式来创建对象,可以帮助减轻这些负担。
构建器模式的简化示例是:
public class Foo
{
public string Prop1 { get; private set; }
public Foo(string prop1)
{
this.Prop1 = prop1;
}
}
[TestClass]
public class FooTests
{
[TestMethod]
public void SomeTestThatRequiresAFoo()
{
Foo f = new Foo("a");
// testy stuff
}
[TestMethod]
public void SomeTestThatRequiresAFooUtilizingBuilderPattern()
{
Foo f = new FooBuilder().Build();
}
[TestMethod]
public void SomeTestThatRequiresAFooUtilizingBuilderPatternOverrideDefaultValue()
{
Foo f = new FooBuilder()
.WithProp1("different than default")
.Build();
}
}
internal class FooBuilder
{
public string Prop1 { get; private set; }
// default constructor, provide default values to Foo object
public FooBuilder()
{
this.Prop1 = "test";
}
// Sets the "Prop1" value and returns this, done this way to create a "Fluent API"
public FooBuilder WithProp1(string prop1)
{
this.Prop1 = prop1;
return this;
}
// Builds the Foo object by utilizing the properties created as BuilderConstruction and/or the "With[PropName]" methods.
public Foo Build()
{
return new Foo(
this.Prop1
);
}
}
这样,如果/当你的Foo对象发生变化时,更新你的单元测试就可以更容易地考虑更改了。
考虑:
public class Foo
{
public string Prop1 { get; private set; }
public string Prop2 { get; private set; }
public Foo(string prop1, string prop2)
{
this.Prop1 = prop1;
this.Prop2 = prop2
}
}
使用此实现,您的单元测试将会中断,但更新构建器比根据Foo的正确构造更新每个单元测试要容易得多
internal class FooBuilder
{
public string Prop1 { get; private set; }
public string Prop2 { get; private set; }
// default constructor, provide default values to Foo object
public FooBuilder()
{
this.Prop1 = "test";
this.Prop2 = "another value";
}
// Sets the "Prop1" value and returns this, done this way to create a "Fluent API"
public FooBuilder WithProp1(string prop1)
{
this.Prop1 = prop1;
return this;
}
// Similar to the "WithProp1"
public FooBuilder WithProp2(string prop2)
{
this.Prop2 = prop2;
return this;
}
// Builds the Foo object by utilizing the properties created as BuilderConstruction and/or the "With[PropName]" methods.
public Foo Build()
{
return new Foo(
this.Prop1,
this.Prop2
);
}
}
使用Foo和FooBuilder的这个新实现,唯一会破坏的单元测试是手动创建Foo的单元测试,使用单元测试的FooBuilder仍然可以正常工作。
这是一个简化的例子,但想象一下,如果你有20-30个单元测试依赖于Foo对象的构造。您可以只更新构建器以正确构造Foo对象,而不是更新这些20-30个单元测试。
在构造函数中对null进行单元测试的示例中,您可以使用构建器模式编写单元测试:
[TestMethod]
public void TestWithNullInFirstParam()
{
Foo f = new FooBuilder()
.WithProp1(null)
.Build()
// in this case "f" will have Prop1 = null, prop2 = "another value"
}