假设你有一个类的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?或者那会超出规格吗?或者它真的没有,因为其他人可能依赖于默认值不会偶然改变?你会为每个构造函数编写一个测试,或者只是默认编译器的测试,因为你知道它会链接所有其他构造函数。
答案 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();
}