【问题标题】:Unit testing code contracts单元测试代码合约
【发布时间】:2011-03-02 10:16:44
【问题描述】:

我只是在玩 .Net 4.0 中的代码合同,但肯定遗漏了一些明显的东西,因为它们的行为与我预期的不同。

我一直使用简单的 if...then.. throw 语句在函数开始时执行任何验证。

if (hours < 0 || hours > 8)
    throw new ArgumentOutOfRangeException("hours", "Hours must be between 0 and 8");

我只是简单地将其替换为

Contract.Requires<ArgumentOutOfRangeException>(hours >= 0 && hours <= 8, "Hours must be between 0 and 8");

但它似乎从来没有在我的单元测试中引发问题。

    public static DurationUnit HoursAsDuration(int hours)
    {
        Contract.Requires<ArgumentOutOfRangeException>(hours >= 0 && hours <= 8, "Hours must be between 0 and 8");

        switch (hours)
        {
            case 1:
            case 2:
                return DurationUnit.Quarter;
            case 3:
            case 4:
                return DurationUnit.Half;
            case 5:
            case 6:
                return DurationUnit.ThreeQuarter;
            case 7:
            case 8:
                return DurationUnit.Full;
            default:
                return DurationUnit.None;
        }
    }

    [Test]
    public void CanConvertToDuration()
    {
        Assert.AreEqual(DurationUnit.None, DateTimeUtility.HoursAsDuration(0));
        Assert.AreEqual(DurationUnit.Quarter, DateTimeUtility.HoursAsDuration(1));
        Assert.AreEqual(DurationUnit.Quarter, DateTimeUtility.HoursAsDuration(2));
        Assert.AreEqual(DurationUnit.Half, DateTimeUtility.HoursAsDuration(3));
        Assert.AreEqual(DurationUnit.Half, DateTimeUtility.HoursAsDuration(4));
        Assert.AreEqual(DurationUnit.ThreeQuarter, DateTimeUtility.HoursAsDuration(5));
        Assert.AreEqual(DurationUnit.ThreeQuarter, DateTimeUtility.HoursAsDuration(6));
        Assert.AreEqual(DurationUnit.Full, DateTimeUtility.HoursAsDuration(7));
        Assert.AreEqual(DurationUnit.Full, DateTimeUtility.HoursAsDuration(8));

        //Would expect this to cause an issue
        Assert.AreEqual(DurationUnit.None, DateTimeUtility.HoursAsDuration(9));
    }

测试返回 true,但我预计代码协定会阻止“9”的值进入 switch 语句。这是预期的行为吗?

【问题讨论】:

    标签: c#-4.0 nunit code-contracts


    【解决方案1】:

    如果您的函数的规范(用英语表示)是它接受 hours 的任何值并在 hours 不在 0..8 范围内时抛出异常,那么它的合约(以代码合约语言表示)并不是说它要求hours 介于0 和8 之间。正确的翻译是该函数不需要任何东西,它确保如果hours 在错误的范围内,则会引发异常,并且它确保如果 hours 在正确的范围内,则完成正确的计算。

    我希望有一种方法可以在代码合同中表达这些东西,但我不熟悉这种合同语言,只熟悉另一种。但是原理是一样的:如果您希望检查成为生产构建的一部分,那么检查的条件不是先决条件。另一方面,合同可能(应该)明示已经进行了检查并且每个案件都得到了适当的处理。

    【讨论】:

    • 感谢 Pascal 让事情变得更清楚了。我相信代码契约可以取代所有参数检查,但如果我希望在生产构建中使用它们,它们仍然需要保留。
    • 更新:下面的 Porges 为我指明了正确的方向,即编辑项目设置以在生产构建中启用合同。
    【解决方案2】:

    好的,我现在通过在 Visual Studio 的项目属性中启用“执行运行时合同检查”选项,让代码抛出了预期的错误。

    这是否意味着当发布到生产环境中时,代码契约会被有效地忽略?

    我显然误解了应该如何使用代码合同,所以如果有人能指出我的最佳实践文章的方向,我将不胜感激。

    msdn 页面声明:

    “合约类中的大多数方法都是有条件编译的;也就是说,只有当您使用#define 指令定义特殊符号 CONTRACTS FULL 时,编译器才会发出对这些方法的调用。CONTRACTS FULL 允许您在您的不使用 #ifdef 指令的代码;您可以生成不同的构建,有些带有合同,有些没有。”

    这是否意味着我仍然会更好地使用 if.. then.. throw.. 来处理任何面向公众的事情?参数验证检查确实有助于减少数据损坏,因为它可以尽早标记故障点。

    【讨论】:

    • 我会对你答案中的所有问题说“是”,我会扩大一点,但 StackOverflow cmets 有点限制,所以我写了另一个答案。
    • rggardner:项目属性中有很多选项 :) 您可以根据需要为发布版本启用运行时强制或禁用它。这取决于您想对合同做什么。您也只能在面向公众的方法等上启用它。
    • 感谢 Porges,了解非常有用。我现在需要做的就是在我的构建文件中使用正确的设置。
    【解决方案3】:

    我认为你需要在你的 hours 参数上交换测试。

    您应该定义将通过的规则。

    所以我认为这对你有用...

    Contract.Requires<ArgumentOutOfRangeException>(hours < 0 && hours > 8, "Hours must be between 0 and 8");
    

    【讨论】:

    • 谢谢,您在我的帖子中发现了一个有点误导的错误。原来的 if.. then.. throw 是错误的,我现在已经更正了。但是问题依然存在。
    • -1:这没有任何意义。您基本上声明您要求 hours 同时小于零和大于8。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    • 2013-04-16
    • 2012-09-02
    • 2016-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多