0

我正在为一个简单的 IsBoolean(x) 函数编写单元测试,以测试一个值是否为布尔值。我要测试 16 个不同的值。

如果我不将它们分解为单独的单元测试,并按如下方式一起运行它们,我会被烧死,还是被 .NET 编程社区无情地嘲笑(这会更糟?):

    [TestMethod]
    public void IsBoolean_VariousValues_ReturnsCorrectly()
    {

        //These should all be considered Boolean values
        Assert.IsTrue(General.IsBoolean(true));
        Assert.IsTrue(General.IsBoolean(false));
        Assert.IsTrue(General.IsBoolean("true"));
        Assert.IsTrue(General.IsBoolean("false"));
        Assert.IsTrue(General.IsBoolean("tRuE"));
        Assert.IsTrue(General.IsBoolean("fAlSe")); 
        Assert.IsTrue(General.IsBoolean(1));
        Assert.IsTrue(General.IsBoolean(0));
        Assert.IsTrue(General.IsBoolean(-1));

        //These should all be considered NOT boolean values
        Assert.IsFalse(General.IsBoolean(null));
        Assert.IsFalse(General.IsBoolean(""));
        Assert.IsFalse(General.IsBoolean("asdf"));
        Assert.IsFalse(General.IsBoolean(DateTime.MaxValue));
        Assert.IsFalse(General.IsBoolean(2));
        Assert.IsFalse(General.IsBoolean(-2));
        Assert.IsFalse(General.IsBoolean(int.MaxValue));
    }

我问这个是因为我一直在阅读的“最佳实践”会要求我执行以下操作:

    [TestMethod]
    public void IsBoolean_TrueValue_ReturnsTrue()
    {
        //Arrange
        var value = true;

        //Act
        var returnValue = General.IsBoolean(value);

        //Assert
        Assert.IsTrue(returnValue);

    }

    [TestMethod]
    public void IsBoolean_FalseValue_ReturnsTrue()
    {
        //Arrange
        var value = false;

        //Act
        var returnValue = General.IsBoolean(value);

        //Assert
        Assert.IsTrue(returnValue);

    }

    //Fell asleep at this point

对于 50 多个函数和 500 多个值,我将对此进行测试似乎完全是浪费时间....但这是最佳实践!!!!!!

-布伦丹

4

5 回答 5

3

我不会担心的。这种事情不是重点。JB Rainsberger 在他的演讲Integration Tests are a Scam中简要谈到了这一点。他说,“如果你从来没有强迫自己每次测试使用一个断言,我建议你尝试一个月。它会给你一个新的测试视角,并教你什么时候每次测试使用一个断言很重要,而当它没有时”。IMO,这属于无关紧要的类别。

顺便说一句,如果你使用 nunit,你可以使用TestCaseAttribute,它更好一点:

[TestCase(true)]
[TestCase("tRuE")]
[TestCase(false)]
public void IsBoolean_ValidBoolRepresentations_ReturnsTrue(object candidate)
{
    Assert.That(BooleanService.IsBoolean(candidate), Is.True);
}

[TestCase("-3.14")]
[TestCase("something else")]
[TestCase(7)]
public void IsBoolean_InvalidBoolRepresentations_ReturnsFalse(object candidate)
{
    Assert.That(BooleanService.IsBoolean(candidate), Is.False);
}

编辑:以稍微不同的方式编写测试,我认为可以更好地传达意图。

于 2013-01-31T03:53:01.390 回答
2

尽管我同意将这些值分开以便更容易地识别错误是最佳做法。我认为人们仍然必须使用自己的常识,并遵循诸如指导原则而不是绝对规则之类的规则。您希望最小化单元测试中的断言计数,但通常最重要的是确保每个测试有一个概念

在您的具体情况下,鉴于功能的简单性,我认为您提供的一个单元测试很好。它易于阅读、简单且清晰。它还彻底测试该功能,如果它在某个地方发生故障,您将能够快速识别源并对其进行调试。

作为额外的说明,为了保持良好的单元测试,您需要始终保持它们是最新的,并像对待实际生产代码一样小心对待它们。这在很多方面都是最大的挑战。进行测试驱动开发的最佳理由可能是它实际上可以让您从长远来看更快地编程,因为您不再担心破坏现有的代码。

于 2013-01-31T03:58:55.517 回答
0

想一想将它们分解为单独测试的原因。这是为了隔离不同的功能,并在测试中断时准确识别出所有出错的地方。看起来您可能正在测试两件事:布尔值和非布尔值,因此如果您的代码遵循两条不同的路径,请考虑两个测试。不过,更重要的一点是,如果没有一个测试失败,那么就没有需要查明的错误。

如果您继续运行它们,然后其中一个测试失败,那么是时候将它们重构为单独的测试,并保持这种状态。

于 2013-02-01T02:41:13.563 回答
0

我无法评论“最佳实践”,因为没有这样的东西

我同意 Ayende Rahien在他的博客中所说的:

最后,归结为这样一个事实,即我不认为测试本身就是对产品的价值。它们唯一的价值是它们的二元能力,可以告诉我产品是否可以。在测试上花费大量额外时间会分散创建真正有价值的可交付软件的注意力。

如果您将它们全部放在一个测试中并且该测试在“某处”失败,那么您会怎么做?您的测试框架将准确地告诉您它在哪一行失败,或者,如果失败,您可以使用调试器逐步完成它。因为这一切都在一个功能中所需的额外努力可以忽略不计。

确切地知道在这个特定实例中哪个测试子集失败的额外价值很小,并且被您必须编写和维护的大量代码所掩盖。

于 2013-01-31T03:53:42.733 回答
0

最好将要测试的每个值拆分为单独的单元测试。每个单元测试都应该根据你传递的值和预期的结果来命名。如果您正在更改代码并且只破坏了一个测试,那么仅该测试就会失败,而其他 15 个测试会通过。这使您能够立即知道您破坏了什么,而无需调试一个单元测试并找出哪些断言失败。

希望这可以帮助。

于 2013-01-31T03:41:42.603 回答