【问题标题】:How do I test code that should never be executed?如何测试不应该执行的代码?
【发布时间】:2010-08-20 21:57:22
【问题描述】:

只有在验证存在无效数字时才调用以下方法(通过调用另一个方法)。如何在以下 sn-p 中测试覆盖 throw-line?我知道一种方法可能是将VerifyThereAreInvalidiDigits 和此方法合并在一起。我正在寻找其他想法。

public int FirstInvalidDigitPosition {
    get {
        for (int index = 0; index < this.positions.Count; ++index) {
            if (!this.positions[index].Valid) return index;
        }
        throw new InvalidOperationException("Attempt to get invalid digit position whene there are no invalid digits.");
    }
}

我也不想编写一个单元测试来练习不应该执行的代码。

【问题讨论】:

  • 你说它不应该被执行并且无法到达的事实对我说“删除它”。您已验证该字符串无效,无法到达此处,因此您知道该属性将返回一些内容。

标签: c# unit-testing testing code-coverage


【解决方案1】:

如果有问题的“throw”语句在任何可能的情况下确实无法访问,那么它应该被删除并替换为:

Debug.Fail("This should be unreachable; please find and fix the bug that caused this to be reached.");

如果代码可以访问,则编写一个单元测试来测试该场景。公共可访问方法的错误报告方案是完全有效的方案。您必须正确处理所有输入,即使是错误的输入。如果正确的做法是抛出异常,请测试您是否正在抛出异常。

更新:根据 cmets,实际上不可能命中错误,因此无法访问代码。但是现在 Debug.Fail 也无法访问,并且无法编译,因为编译器注意到返回值的方法具有可到达的端点。

第一个问题实际上不应该是一个问题;当然,代码覆盖工具应该可以配置为忽略无法访问的仅调试代码。但是这两个问题都可以通过重写循环来解决:

public int FirstInvalidDigitPosition 
{ 
    get 
    { 
        int index = 0;
        while(true) 
        {
            Debug.Assert(index < this.positions.Length, "Attempt to get invalid digit position but there are no invalid digits!");
            if (!this.positions[index].Valid) return index; 
            index++;
        } 
    }
}

另一种方法是重新组织代码,这样一开始就没有问题:

public int? FirstInvalidDigitPosition { 
    get { 
        for (int index = 0; index < this.positions.Count; ++index) { 
            if (!this.positions[index].Valid) return index; 
        } 
        return null;
    } 
} 

现在您不需要限制调用者先调用 AreThereInvalidDigits;只要让任何时候调用这个方法都是合法的。这似乎是更安全的做法。如果你不做一些昂贵的检查来验证它们是否可以安全调用,就会崩溃的方法是脆弱的、危险的方法。

【讨论】:

  • 我应该提到,我正在做 TDD,这使我免于测试虚假案例:P 这是一个类公共,但模块内部的方法。因此,抛出异常不是要求的一部分,因此没有测试。
  • 同样 Debug.Fail 仍然不会被代码覆盖,同样用 Debug.Fail 替换 throw 是一个编译错误。
  • 如果 Jon Skeet 发布了完全相同的答案,那么他现在将获得 4 倍的赞成票。是不是很棒?
  • 哦,为什么 C# 没有 ShouldlNeverGetHere 异常?
【解决方案2】:

我不明白你为什么不想编写一个单元测试来练习“不应该发生的代码”。

如果它“不应该发生”,那你为什么要编写代码?因为您认为它当然会发生 - 也许未来重构中的错误会破坏您的其他验证并且将执行此代码。

如果您认为值得编写代码,则值得对其进行单元测试。

【讨论】:

  • 我同意 - 编写“永远不应该执行”的代码对我来说听起来像是一种浪费的练习。
  • 好点。我编写该代码是因为 C# 要求非 void 方法以 return 或 throw 结束。我宁愿在运行时抛出运行时异常。所以我不必写那条丑陋的线。这意味着我需要语言功能的解决方法,或者找到设计中的缺陷。
  • 为了改进设计,我会使用方法而不是属性。属性“看起来”像访问调用者的简单成员变量,因此如果执行需要很长时间,它们可能会惊讶,具有副作用(例如更改 'get 中的成员变量' 调用),导致事件被触发,或引发异常。另一方面,方法返回值以指示“未找到”(例如 -1,如 string.IndexOf)或抛出异常是正常的 - 因此方法是更好的设计选择。
  • 我认为问题在于您不想假设实际上不可能到达那里,但与此同时,您只是无法通过测试到达那里。不是你不想测试它,而是没有“正常”的方法可以到达那里。
【解决方案3】:

有时需要编写一些无法执行的代码,只是为了安抚编译器(例如,如果从应该返回的函数调用总是抛出异常、退出应用程序等的函数一个值,编译器可能会坚持调用者包含一个永远不会执行的“return 0”,“Return Nothing”等语句。我认为不应该要求测试这样的“代码”,因为它可能是如果不严重破坏系统的其他部分,就不可能让它执行。

一个更有趣的问题是处理器在正常情况下永远无法执行的代码,但存在这些代码是为了最大限度地减少异常情况造成的损害。例如,我编写的一些安全关键应用程序从代码开始,例如(真正的应用程序是机器代码;下面给出的是伪代码)

寄存器 = 0 测试寄存器为零 如果非零 goto dead 寄存器 = 寄存器 - 1 测试寄存器为零 如果非零 goto OK1 死的: 关闭一切 死去 好的1: 寄存器 = 寄存器 + 1 如果非零 goto dead

我不确定如何在失败的情况下测试该代码。该代码的目的是确保如果收银机受到静电或其他损坏,或者无法正常工作,系统将安全关闭。但是,由于缺少一些非常精美的设备,我不知道如何测试此类代码。

【讨论】:

    【解决方案4】:

    测试的部分目的是测试应该发生的事情和可能发生的事情。

    如果您的代码不应该执行,但可以在正确(或错误)的条件下,您可以验证异常发生在正确的条件下(在MS 的单元测试框架)通过以下方式装饰您的测试方法:

    [ExpectedException(typeof(InvalidOperationException))]
    

    【讨论】:

    • 我正在做 TDD,因此我不会测试可以但绝不不应该发生的事情。在这种情况下,该异常表明代码中存在错误,而不是异常情况。
    • 重申您所写内容的一种方法是,有些事情您知道可能发生,但您却无所作为!由于我不认为你想让你的程序对未定义的行为开放,你可能需要更多地考虑你的测试和编码策略!在可行的情况下,您的要求应该是令人满意地处理所有可能的输入,包括不受欢迎的输入。编写测试以验证您的程序是否满足这些要求。
    【解决方案5】:

    换个说法,基本上是:当您已经费了很多力气来确保系统一开始就不会进入无效状态时,如何测试系统的无效状态?

    这会使测试稍微慢一些,但我建议使用反射将您的系统置于无效状态以便对其进行测试。

    还请注意,反射正是您的系统进入无效状态的一种可能方式,这将执行您认为不可能到达的代码。

    如果您的代码确实无法访问,而据我所见,您的示例并非如此,那么您应该删除该代码。例如:

    public int GetValue() {
       return 5;
       throw new InvalidOperationException("Somehow the return statement did not work!");
    }
    

    我也不想编写一个单元测试来执行不应该执行的代码。

    这里的“应该”是什么意思?比如,它不应该被执行?当然,在 test 中执行代码应该没问题。它应该在测试中执行。

    【讨论】:

      猜你喜欢
      • 2016-08-24
      • 2015-05-28
      • 1970-01-01
      • 1970-01-01
      • 2013-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-05
      相关资源
      最近更新 更多