C#:你如何编写构造函数和构造函数重载的测试用例?

时间:2009-11-09 18:20:22

标签: c# unit-testing constructor

假设你有一个类的shell:

public class Number
{
    private int value;

    public Number()
        : this(0) {}

    public Number(int initialValue)
        : this(initialValue, 0, 100) {}

    public Number(int initialValue, int minimumValue, int maximumValue)
    {
        if (minimumValue > maximumValue)
            throw new ArgumentException("Minimum cannot be greater than maximum", "minimumValue");

        MinValue = minimumValue;
        MaxValue = maximumValue;
        Value = initialValue;
    }

    public int MinValue { get; private set; }
    public int MaxValue { get; private set; }

    public int Value
    {
        get { return value; }
        set
        {
            if (value < MinValue)
                value = MinValue;
            if (value > MaxValue)
                value = MaxValue;

            this.value = value;
        }
    }
}

你会为这个课程编写测试吗?如果是的话,你会怎么写它们?

我正在考虑构造函数。比如,您是否有一个测试使用默认构造函数创建了一个Number并检查该值是否为0,minvalue为0且maxvalue为100?或者那会超出规格吗?或者它真的没有,因为其他人可能依赖于默认值不会偶然改变?你会为每个构造函数编写一个测试,或者只是默认编译器的测试,因为你知道它会链接所有其他构造函数。

6 个答案:

答案 0 :(得分:3)

我已经完全从TDD的经典方法转向更现代和逻辑的BDD(行为驱动设计)。对于Number类,我会编写以下BDD规范(请注意,下面的语法是使用SubSpec完成的,它依赖于xUnit.NET):

public void Parameterless_constructor_initializes_all_defaults_properly()
{
    // State
    Number number = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with no parameters".Do(() => { number = new Number(); });

    // Observations
    "the Value property should contain the default value 0".Assert(() => Assert.Equal(0, number.value));
    "the MinValue property should be 0".Assert(() => Assert.Equal(0, number.MinValue));
    "the MaxValue property should be 100".Assert(() => Assert.Equal(100, number.MaxValue));
}

public void Single_parameter_constructor_initializes_all_defaults_and_initial_value_properly()
{
    // State
    Number number = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with the initial value".Do(() => { number = new Number(10); });

    // Observations
    "the Value property should contain the value 10".Assert(() => Assert.Equal(10, number.value));
    "the MinValue property should be 0".Assert(() => Assert.Equal(0, number.MinValue));
    "the MaxValue property should be 100".Assert(() => Assert.Equal(100, number.MaxValue));
}

public void Full_constructor_initializes_all_values_properly()
{
    // State
    Number number = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with the initial, min, and max values".Do(() => { number = new Number(10, 1, 50); });

    // Observations
    "the Value property should contain the value 10".Assert(() => Assert.Equal(10, number.value));
    "the MinValue property should be 1".Assert(() => Assert.Equal(1, number.MinValue));
    "the MaxValue property should be 50".Assert(() => Assert.Equal(50, number.MaxValue));
}

此外,我注意到当最小值大于最大值时,您的完整构造函数也可能存在异常情况。在这种特殊情况下,您还需要验证正确的行为:

public void Full_constructor_throws_proper_exception_when_minvalue_greater_than_maxvalue()
{
    // State
    Number number = null;
    Exception expectedEx = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with inverted min and max values".Do(
        () => 
        { 
            try { number = new Number(10, 50, 1); }
            catch (Exception ex) { expectedEx = ex }
        }
     );

    // Observations
    "an exception should be thrown".Assert(() => Assert.NotNull(expectedEx));
    "the exception should be an ArgumentException".Assert(() => Assert.IsType<ArgumentException>(expectedEx));
}

以上规格应为您提供100%的测试覆盖率。当使用xunit.net执行时,它们还会生成一个非常好的,人类可读的逻辑报告,并输出默认报告。

答案 1 :(得分:1)

我猜你有几个构造函数是有原因的 - 尝试测试场景而不是根据某些规则初始化类。
例如,如果使用默认构造函数为即时计算测试创建一个类,而不是默认构造函数具有某个值集的事实。

我的观点是你不应该有你不使用的重载(除非你正在开发API),所以为什么不测试用例而不是构造函数。

答案 2 :(得分:0)

使用nunit。

创建一个为每个构造函数创建对象的测试。使用Assert.AreEqual确保所有对象都相等(你应该为这样的类重写Equals)。为了更加确定,否定断言Assert.AreSame断言。

然后测试每个属性的正确值。

如果您的课程更复杂并且您想要更加小心,那么您可以将所有值设置为唯一的随机数,并声明属性是从随机数据集中正确初始化的。

答案 3 :(得分:0)

我会为每个构造函数编写一个单元测试,检查是否正确设置了最小值和最大值。我会这样做以确保如果我稍后更改其中一个构造函数的代码,我的测试会告诉我在哪里改变了。
我也可以将默认的最小值和最大值提取到常量中,以便测试看起来像Assert.AreEqual(DefaultMinimum,myNumber.MinValue)。
我会检查一个无效的min / max引发异常的测试 我会将这个类重命名为“BoundedNumber”或类似的东西:)

答案 4 :(得分:0)

好的,我真的回答了代码清单下面的问题(而不是标题中的问题)......

我认为这个类的主要价值在于它的(双关语)Value属性。因此,应该成为单元测试的焦点的是属性而不是构造函数。

如果在编写三个构造函数之后编写单元测试并使这些测试过于严格(过度规范),那么您最终会面临一系列脆弱,难以维护的测试。

答案 5 :(得分:0)

构造函数的所有测试都是类似的,因为构造函数所做的事情在定义上是相似的。所以我写了一个简单的测试库,它有助于为构造函数编写声明性测试:How to Easily Test Validation Logic in Constructors in C#

这是一个例子,我在一个类的构造函数上尝试七个测试用例:

[TestMethod]
public void Constructor_FullTest()
{

    IDrawingContext context = new Mock<IDrawingContext>().Object; 

    ConstructorTests<Frame>
        .For(typeof(int), typeof(int), typeof(IDrawingContext))
        .Fail(new object[] { -3, 5, context }, typeof(ArgumentException), "Negative  length")
        .Fail(new object[] { 0, 5, context }, typeof(ArgumentException), "Zero length")
        .Fail(new object[] { 5, -3, context }, typeof(ArgumentException), "Negative width")
        .Fail(new object[] { 5, 0, context }, typeof(ArgumentException), "Zero width")
        .Fail(new object[] { 5, 5, null }, typeof(ArgumentNullException), "Null drawing context")
        .Succeed(new object[] { 1, 1, context }, "Small positive length and width")
        .Succeed(new object[] { 3, 4, context }, "Larger positive length and width")
        .Assert();

}