【问题标题】:.NET 4.0 code contracts - How will they affect unit testing?.NET 4.0 代码合同——它们将如何影响单元测试?
【发布时间】:2010-11-25 21:27:16
【问题描述】:

例如这个article 介绍了他们。

有什么好处?

静态分析看起来很酷,但同时它会阻止在单元测试中将 null 作为参数传递的能力。 (如果您按照文章中的示例进行操作)

关于单元测试的话题——考虑到现在的情况,如果你已经练习过自动化测试,那么代码合同肯定没有意义吗?

更新

玩过 Code Contracts 我有点失望。例如,根据接受答案中的代码:

public double CalculateTotal(Order order)
{
    Contract.Requires(order != null);
    Contract.Ensures(Contract.Result<double>() >= 0);
    return 2.0;
}

对于单元测试,您仍然必须编写测试以确保不能通过 null,并且如果合同是 业务逻辑,则结果大于或等于 0 >。换句话说,如果我要删除第一个合同,任何测试都不会中断,除非我专门对此功能进行了测试。然而,这是基于不使用更好(最终等)版本的 Visual Studio 中内置的静态分析。

基本上,它们都归结为编写传统 if 语句的另一种方式。我实际使用TDD, with Code Contracts 的经验说明了原因以及我是如何做到的。

【问题讨论】:

  • 为什么还要写测试保证null不能通过?是因为你觉得你不能信任静态验证者吗?可能值得注意的是 order 为 null 对该方法的功能没有影响,因此我个人认为该参数的存在是一个错误。如果您实际上是在使用订单执行一些计算,那么我希望其他一些测试会因 NullReferenceException 而中断。
  • @StephenJ.Anderson +1 对order 的代码审查评论很好。

标签: unit-testing .net-4.0 code-contracts microsoft-contracts


【解决方案1】:

合约允许你说出代码的实际目的是什么,而不是让代码执行的任何随机参数都被传递给它作为从编译器或下一个读者的角度来看的定义代码。这可以显着改善静态分析和代码优化。

例如,如果我声明一个整数参数(使用协定表示法)在 1 到 10 的范围内,并且我在函数中声明了一个相同大小的本地数组,该数组由参数索引,则编译器可以判断没有下标错误的可能性,从而产生更好的代码。

您可以声明 null 是合同中的有效值。

单元测试的目的是验证动态代码实现了它所声明的任何目的。仅仅因为您已经为函数编写了合约,并不意味着代码可以做到这一点,或者静态分析可以验证代码是否可以做到这一点。单元测试不会消失。

【讨论】:

  • +1 你是对的。合同对其他开发人员和静态/运行时分析很有帮助,始终假定合同是正确的——除非您进行了适当的单元测试,否则您无法真正知道这一点。在合同之外,单元测试会验证即使满足合同条件,代码仍然会按照您的意图执行。
【解决方案2】:

一般来说它不会干扰单元测试。但正如我所见,您提到了一些关于 TDD 的内容。

如果我从这个角度考虑,我猜它可能/可能会改变标准程序的程序

  • 创建方法(只是签名)
  • 创建单元测试 -> 实施测试
  • 运行测试:让它失败
  • 实现该方法,将其修改到最后以使其正常工作
  • 运行测试:看它通过
  • 重构您的(可能是混乱的)方法体
  • (重新运行测试只是为了看看你没有破坏任何东西)

这将是真正困难的全功能单元测试过程。在这种情况下,我猜您可以在第一点和第二点之间插入代码合同,例如

  • 创建方法(只是签名)
  • 为方法输入参数插入代码协定
  • 创建单元测试 -> 实施测试
  • ...

我目前看到的优势是您可以编写更简单的单元测试,因为您不必检查所有可能的路径,因为您定义的合同已经考虑了一些路径。它只是给你额外的检查,但它不会取代单元测试,因为代码中总会有更多的逻辑,更多的路径必须像往常一样用单元测试来测试。

编辑

我之前没有考虑过的另一种可能性是在重构部分添加代码协定。基本上作为保证事情的额外方式。但这在某种程度上是多余的,因为人们不喜欢做多余的事情......

【讨论】:

  • 您还会进行测试以测试通过合同确认的内容吗?
  • 我想我不会测试那些已经被合同保证的东西。只是以某种方式“重复”工作。因此,我也将它们放在了在 TDD 周期中实际编写单元测试之前。
  • @Juri,如果您输入错误的合同条件怎么办,例如Contract.Requires(x &lt; 2)...哎呀,意思是x &lt; 1?如果没有测试,您怎么知道您的代码是否真的在做您认为它正在做的事情?我认为合同是不够的。合同不能代替测试。借助 IDE 支持,他们可以让其他用户知道可以预期哪些前置/后置条件(假设这些条件是正确的)。运行时/静态检查器可以执行分析(再次假设合同是正确的)。除此之外,您还需要测试。
  • 如果你要写一个糟糕的合同,什么会阻止你写一个糟糕的测试?
【解决方案3】:

有什么好处?

假设你想确保一个方法永远不会返回null。现在使用单元测试,您必须编写一堆测试用例,在其中调用具有不同输入的方法并验证输出不为空。 问题是,您无法测试所有可能的输入。

使用代码契约,您只需声明该方法永远不会返回null。如果无法证明这一点,静态分析器就会抱怨。如果它没有抱怨,您就知道您的断言对于所有可能的输入都是正确的。

更少的工作,完美的正确性保证。有什么不喜欢的?

【讨论】:

  • 代码契约基本上是代码本身的单元测试......我遇到的问题是你如何为算法编写契约?假设您有一个校验和验证器方法ValidateChecksum(inputWithChecksum)。要提供完整的合同,您必须重新创建算法,以确保代码的作用。如果你的算法有漏洞,你的合约也可能有漏洞。那么是什么给出的呢?
  • @RobertKoritnik:合约不必指定完整的行为,只指定和证明代码的某些属性(比如不返回 null)已经很有用了。为了确保计算准确地给出预期的输出,单元测试仍然是要走的路。
【解决方案4】:

我不认为单元测试和合同相互干扰太多,如果有什么合同应该有助于单元测试,因为它消除了为无效参数添加繁琐重复测试的需要。契约指定了你可以从函数中得到的最小值,而单元测试试图验证一组特定输入的实际行为。考虑这个人为的例子:


public class Order
{
    public IEnumerable Items { get; }
}

public class OrderCalculator
{
    public double CalculateTotal(Order order)
    {
        Contract.Requires(order != null);
        Contract.Ensures(Contract.Result<double>() >= 0);

        return 2.0;
    }
}

很明显,代码满足合同要求,但您仍需要进行单元测试来验证它的实际行为是否符合您的预期。

【讨论】:

  • 如果你有静态验证工具,你真的不需要对合约指定的内容进行单元测试。 Visual Studio 2010 团队版将包括那些用于 .NET 代码合同的工具。如果静态验证器没有发出任何警告或错误,则代码满足合约。
  • 单元测试和合同不是相互排斥的。合约做了测试不能做的事情,测试需要确保合约是正确的并且调用代码遵守它们。仅仅因为您有 Requires 语句并不意味着您不应该对该条件进行测试。
  • 我得出的结论是,编写测试以确保您的合同“正确”是不值得的。您正在创建合同这一事实意味着您正在考虑 null 是否是一个有效参数。测试先决条件正在将注意力从真正重要的事情上转移开。该功能实际上是做什么的。我认为可以通过添加将合同放在界面上的示例来改进答案。你真的会为所有实现构建一个单元测试来确保 null 被拒绝吗?所以,+1 这个答案。
  • 我认为示例中提供的代码契约是可以的,但它们并不完整。就好像您要编写测试相同的单元测试一样。总和大于或等于 0。这意味着您的合同不完整。 AFAIK 合约应该同样完整,因为它们应该提供代码执行将提供预期结果的所有前置/后置条件。我认为您的代码并不是合同使用的一个很好的例子。我强烈建议您也检查一下 NUnit 理论 的作用,因为它们本质上与合约非常相似,只是它们是以测试的形式呈现的。
  • 我完全同意@wekempf。如果我对合同条件过分粗暴怎么办?合同正确吗?不,我会知道吗?可能是。但也许还不够好。像单元测试这样的东西实际上不会告诉我代码是否按我预期的那样工作。当然,我也可能打错单元测试。但很有可能,该测试最终会因此而失败。但是有了合约,我的代码可能永远不会失败,然而,我原本要求的条件最终可能由于拼写错误而永远无法满足。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多