【问题标题】:Unit Test Null Parameters in Constructor构造函数中的单元测试空参数
【发布时间】:2015-01-26 14:52:47
【问题描述】:

我已经为一个类编写了一个构造函数,并且我正在测试每个参数是否为空。请参见下面的示例:

public MyClass(IObjectA objA, IObjectB objB) : IMyClass
{
    if (objA == null)
    {
        throw new ArgumentNullException("objA");
    }

    if (objB == null)
    {
        throw new ArgumentNullException("objB");
    }

    ...
}

通常我通过模拟 IObjectAIObjectB 并传入它们来对此进行单元测试(使用 Moq)。上面的示例将创建 2 个单元测试来测试每个场景。

我遇到的问题是第三个参数被传递到构造函数时。它要求我更改以前的测试,因为我突然得到“MyClass 的构造函数没有 2 个参数”类型异常。

我也使用 AutoMockContainer。本质上,我希望能够通过在容器中注册一个空对象来测试构造函数。例如:

[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&lt;T&gt;() 方法抛出 ArgumentNullException 而不是“Act”部分中执行的代码。

有没有人建议能够测试构造函数参数而不必在以后添加新参数时重新访问单元测试?

【问题讨论】:

  • 如果你显式地测试构造函数,那么当你改变构造函数的契约时测试将不得不改变。任何被测功能也是如此。恐怕这就是生活。
  • 您是否在应用程序中使用 IoC 容器将依赖项注入此类?如果是这样,请质疑编写(因此必须维护)许多测试您的基础设施的测试(可能是数百个)的价值。我研究的一个解决方案有数千个测试,所有测试都测试空 ctor 参数,维护起来简直就是一场噩梦。
  • @Matt 很遗憾,我的客户想要 100% 的测试覆盖率。我自己并不关心测试覆盖率统计数据,通常不会测试这些东西或接线。我喜欢防御性编程,但有时当我在试图实现 100% 覆盖率的地方工作时,我觉得我只是在为自己做事。

标签: c# moq automoq


【解决方案1】:

您可以通过使用工厂模式或构建器模式来创建对象来帮助减轻部分负担。

构建器模式的简化示例如下:

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"
}  

【讨论】:

  • 非常有趣,绝对值得消化和考虑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-23
  • 1970-01-01
  • 2018-09-14
  • 2017-09-15
  • 1970-01-01
  • 1970-01-01
  • 2019-04-20
相关资源
最近更新 更多