C# 构造函数中的单元测试空参数

C# 构造函数中的单元测试空参数,c#,moq,automoq,C#,Moq,Automoq,我已经为一个类编写了一个构造函数,并且正在测试每个参数是否为null。见下例: public MyClass(IObjectA objA, IObjectB objB) : IMyClass { if (objA == null) { throw new ArgumentNullException("objA"); } if (objB == null) { throw new ArgumentNullException(

我已经为一个类编写了一个构造函数,并且正在测试每个参数是否为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>();
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Construction ThrowsExceptionForNullobja()
{
//安排。
var container=newautomockcontainer(newmockrepository(MockBehavior.Default));
容器。寄存器(空);
//表演。
var sut=container.Create();
}
那么,向构造函数添加多少新参数就无关紧要了。我将不必更新我的单元测试

遗憾的是,上面的单元测试通过了。但是因为错误的原因。
Register()
方法抛出的是
ArgumentNullException
而不是在“Act”部分执行的代码


是否有人建议能够测试构造函数参数,并且在以后添加新参数时不必重新进行单元测试?

您可以通过使用factory或builder模式创建对象来帮助减轻一些负担

生成器模式的简化示例如下:

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对象的构造。不需要更新那些20-30个单元测试,只需更新构建器即可正确构建Foo对象

在构造函数中对null进行单元测试的示例中,可以使用生成器模式编写单元测试,如下所示:

[TestMethod]
public void TestWithNullInFirstParam()
{
    Foo f = new FooBuilder()
        .WithProp1(null)
        .Build()

    // in this case "f" will have Prop1 = null, prop2 = "another value"
}  

如果您显式地测试构造函数,那么当您更改构造函数的契约时,测试必须更改。这同样适用于任何正在测试的函数。恐怕这就是生活。你是否在应用程序中使用IoC容器向这个类中注入dedencies?如果是这样,请质疑编写(因此必须维护)许多测试(可能是数百个)来测试您的基础架构的价值。我所使用的一个解决方案有数千个测试,所有测试都是空的参数,维护它成了一场噩梦。@Matt遗憾的是,我的客户想要100%的测试覆盖率。我自己并不担心测试覆盖率统计数据,通常也不会测试这些东西或线路。我喜欢防守,但有时当我在100%的覆盖范围内工作时,我觉得我只是在为自己制造困难。非常有趣的是,一定要消化和思考。