C# 检测';死亡';测试和硬编码数据与约束非确定性
对于那些不确定“受约束的非决定论”是什么意思的人,我推荐马克·西曼的 这个想法的本质是,测试只对影响SUT行为的数据具有确定性值。非“相关”数据在某种程度上可能是“随机”的 我喜欢这种方法。数据越抽象,期望就越清晰、越有表现力,事实上,在无意识的情况下使数据符合测试就越困难。C# 检测';死亡';测试和硬编码数据与约束非确定性,c#,tdd,autofixture,C#,Tdd,Autofixture,对于那些不确定“受约束的非决定论”是什么意思的人,我推荐马克·西曼的 这个想法的本质是,测试只对影响SUT行为的数据具有确定性值。非“相关”数据在某种程度上可能是“随机”的 我喜欢这种方法。数据越抽象,期望就越清晰、越有表现力,事实上,在无意识的情况下使数据符合测试就越困难。 我正试图向我的同事“推销”这种方法,昨天我们对此进行了长时间的辩论。 他们提出了一个有趣的论点:由于随机数据,测试不稳定,调试困难。 起初,这似乎有点奇怪,因为我们都同意,影响数据流的数据不能是随机的,这样的行为是不可
我正试图向我的同事“推销”这种方法,昨天我们对此进行了长时间的辩论。
他们提出了一个有趣的论点:由于随机数据,测试不稳定,调试困难。
起初,这似乎有点奇怪,因为我们都同意,影响数据流的数据不能是随机的,这样的行为是不可能的。尽管如此,我还是休息了一下,仔细思考了这个问题。 最后我遇到了以下问题: 但首先我的一些假设:
[TestMethod]
public void DoSomethig_RetunrsValueIncreasedByTen()
{
// Arrange
ver input = 1;
ver expectedOutput = input+10;
var sut = new MyClass();
// Act
var actualOuptut = sut.DoeSomething(input);
// Assert
Assert.AreEqual(expectedOutput,actualOutput,"Unexpected return value.");
}
/// Here nothing is changed besides input now is random.
[TestMethod]
public void DoSomethig_RetunrsValueIncreasedByTen()
{
// Arrange
var fixture = new Fixture();
ver input = fixture.Create<int>();
ver expectedOutput = input+10;
var sut = new MyClass();
// Act
var actualOuptut = sut.DoeSomething(input);
// Assert
Assert.AreEqual(expectedOutput,actualOutput,"Unexpected return value.");
}
[TestMethod]
公共无效剂量值增加的字节()
{
//安排
ver输入=1;
预期输出电压=输入+10;
var sut=新的MyClass();
//表演
var actualuptut=sut.DoeSomething(输入);
//断言
AreEqual(expectedOutput,actualOutput,“意外返回值”);
}
///这里除了输入是随机的外,没有任何改变。
[测试方法]
公共无效剂量值增加的字节()
{
//安排
var fixture=新fixture();
ver输入=fixture.Create();
预期输出电压=输入+10;
var sut=新的MyClass();
//表演
var actualuptut=sut.DoeSomething(输入);
//断言
AreEqual(expectedOutput,actualOutput,“意外返回值”);
}
到目前为止,上帝,一切都在运转,生活都是美好的,但随后需求发生了变化,做一些事情也改变了它的行为:现在只有当它小于10时,它才会增加投入,否则它就会乘以10。
这里发生了什么?硬编码数据的测试通过(实际上是偶然的),而第二个测试有时失败。它们都是错误的欺骗测试:它们检查不存在的行为
看起来不管数据是硬编码的还是随机的,都无关紧要:只是无关紧要。然而,我们没有可靠的方法来检测这种“死”测试
因此,问题是:
有没有什么好的建议,如何以这种情况不出现的方式编写测试?答案实际上隐藏在这句话中: […]然后需求改变,剂量测量改变其行为[…] 如果你这样做会不会更容易:
- 首先更改
,以满足新的要求预期输出
- 观察失败的测试,看到它失败是很重要的
- 只有根据新的要求修改
,才能使测试再次通过DoSomething
那么AutoFixture在哪里真正有用呢?使用AutoFixture,可以最小化测试的排列部分 以下是原始测试,使用以下惯用方法编写:
[理论,InlineAutoData]
公共无效数据量输入超过10返回正确结果(
我的类sut,
[范围(int.MinValue,9)]int输入)
{
Assert.True(输入<10);
预期风险值=输入+1;
var实际值=总剂量(输入);
断言。相等(预期、实际);
}
[理论,InlineAutoData]
公共无效DoSomethingInputSequals或创建10ReturnsCorrectResult时(
我的类sut,
[范围(10,int.MaxValue)]int输入)
{
Assert.True(输入>=10);
预期var=输入*10;
var实际值=总剂量(输入);
断言。相等(预期、实际);
}
另外,除了xUnit.net,还有
嗯
“然后需求会改变,而DoSomething会改变其行为”
真的吗?如果DoSomething
改变了行为,则违反了(OCP)。你可能决定不关心这件事,但它与你的生活息息相关
每次更改现有测试时,都会降低它们的可信度。每次更改现有生产行为时,都需要检查所有涉及该生产代码的测试。理想情况下,您需要访问每个这样的测试,并简要地更改实现,以确保在实现错误时仍然失败
对于小的变化,这可能仍然是可行的,但是对于中等的变化,坚持OCP会更明智:不要改变现有的行为;同时添加新行为,让旧行为萎缩
在上面的示例中,很明显AutoFixture测试可能是非决定性错误,但在更概念化的层面上,完全有可能,如果您在不检查测试的情况下更改生产行为,某些测试可能会悄悄地变成错误。这是一个与单元测试相关的一般问题,而不是特定于AutoFixture。好的,这里有一个问题。当分支条件变得非常复杂时,通常会创建几个测试-每个条件分支都有一个测试。您可以为新的分支编写测试,但是忘记旧的方法我非常同意您的观点,但是我认为最好在测试数据生成器中表达工作流期望。最初,它将通过方法“GetSomeInput”返回数据。稍后,当行为发生变化时,我们将删除该方法并添加另外两个“GetLessThan10”和“GetEqualOrgCreateThan10”。删除第一个方法会破坏初始测试:它不会编译@
[Theory, InlineAutoData]
public void DoSomethingWhenInputIsLowerThan10ReturnsCorrectResult(
MyClass sut,
[Range(int.MinValue, 9)]int input)
{
Assert.True(input < 10);
var expected = input + 1;
var actual = sut.DoSomething(input);
Assert.Equal(expected, actual);
}
[Theory, InlineAutoData]
public void DoSomethingWhenInputIsEqualsOrGreaterThan10ReturnsCorrectResult(
MyClass sut,
[Range(10, int.MaxValue)]int input)
{
Assert.True(input >= 10);
var expected = input * 10;
var actual = sut.DoSomething(input);
Assert.Equal(expected, actual);
}