【问题标题】:static methods and unit tests静态方法和单元测试
【发布时间】:2010-11-18 23:45:20
【问题描述】:

我一直在阅读,使用 TDD 时往往会避免使用静态方法,因为它们往往难以模拟。但我发现,最容易进行单元测试的是具有简单功能的静态方法。不必实例化任何类,鼓励方法简单、只做一件事、“独立”等。

有人能解释一下 TDD 最佳实践和实用性之间的差异吗?

谢谢, 一个

【问题讨论】:

    标签: c++ unit-testing


    【解决方案1】:

    静态方法很容易测试,但是直接调用静态方法的东西一般不依赖于它所依赖的静态方法来测试是不容易的。使用非静态方法,您可以使用 stub/mock/fake 实例来简化测试,但如果您正在测试的代码调用静态方法,它实际上是“硬连线”到该静态方法。

    【讨论】:

    • 我认为您正在寻找的术语是紧密耦合的。
    • @Martin:谢谢,我知道这个词,尽管它比我在这里得到的更笼统。常用术语往往最终几乎失去意义,因为人们(ab)使用它们太多,所以我选择在这里使用一个比喻,希望它更清楚。
    • 第一次听到“硬连线”代替紧密耦合。术语的变化如何有助于理解,这绝对是绝妙的。
    【解决方案2】:

    在我看来,对所提问题的回答是“面向对象似乎是人们思考 TDD 的全部内容。”

    为什么?我不知道。也许他们都是 Java 程序员,感染了让一切都依赖于六个间接层、依赖注入和接口适配器的疾病。

    Java 程序员似乎喜欢预先把所有事情都弄得很困难,以便“以后节省时间”。

    我建议在您的 TDD 中应用一些敏捷原则:如果它没有导致问题,则不要修复它。不要过度设计。

    在实践中,我发现如果静态方法首先经过良好测试,那么它们不会成为调用者出现错误的原因。

    如果静态方法执行速度很快,那么它们就不需要模拟。

    如果静态方法与程序外部的东西一起工作,那么您可能需要一个模拟方法。在这种情况下,您需要能够模拟许多不同类型的函数行为。

    如果您确实需要模拟静态方法,请记住在 OO 编程之外可以使用它。

    例如,您可以编写脚本来将源代码处理为调用模拟函数的测试表单。您可以将具有不同版本功能的不同目标文件链接到测试程序中。您可以使用链接器技巧来覆盖函数定义(如果它没有被内联)。我敢肯定还有一些我没有在这里列出的技巧。

    【讨论】:

    • 在简单的情况下我会同意你的看法。许多静态方法是简单的实用程序调用,它们是静态的,因为它们没有状态。但是其他静态调用引用全局(静态)变量或其他静态调用,这在单元测试环境中可能很难管理。您必须确保每次测试运行的全局状态都是正确的 - 否则您会变得脆弱(孤立地工作但在套件的其余部分运行时失败的测试)。所以它可能是一个蠕虫罐头,但我同意你应该务实。
    【解决方案3】:

    测试静态方法很容易。问题是在测试其他代码时,无法将其他代码与该静态方法隔离开来。调用代码与静态代码紧密耦合。

    对静态方法的引用不能被许多模拟框架模拟,也不能被覆盖。

    如果您有一个进行大量静态调用的类,那么要对其进行测试,您必须为所有这些静态调用配置应用程序的全局状态 - 因此维护成为一场噩梦。如果你的测试失败了,那么你就不知道是哪一段代码导致了失败。

    弄错了,是许多开发人员认为 TDD 是无稽之谈的原因之一。他们为测试结果投入了巨大的维护工作,这些结果只能模糊地表明出了什么问题。如果他们只是减少代码单元之间的耦合,那么维护将是微不足道的,并且测试结果是特定的。

    【讨论】:

      【解决方案4】:

      这个建议在很大程度上是正确的.. 但并非总是如此。我的 cmets 不是 C++ 特定的..

      1. 为静态方法(它们是纯/无状态函数)编写测试:即处理输入以产生一致的结果。例如在下面添加 - 给定一组特定的输入,将始终给出相同的值。为这些或调用此类纯静态方法的代码编写测试没有问题
      2. 使用静态的静态方法编写测试:例如GetAddCount() 下面。在多个测试中调用它可以产生不同的值。因此,一项测试可能会损害另一项测试的执行——测试需要独立。所以现在我们需要引入一种方法来重置静态状态,这样每个测试都可以从一个干净的状态开始(例如,像 ResetCount() 之类的东西)。
      3. 为访问静态方法的代码编写测试没有对依赖项的源代码访问:再次依赖于静态方法本身的属性。但是,如果它们很粗糙,那么您将很难依赖。如果依赖项是一个对象,那么您可以向依赖类型添加一个 setter 并为您的测试设置/注入一个假对象。当依赖项是静态的时,您可能需要进行相当大的重构才能使测试可靠地运行。 (例如,添加一个委托给静态方法的对象中间人依赖项。现在为您的测试插入一个假中间人)

      举个例子

      public class MyStaticClass
      {
        static int __count = 0;
        public static int GetAddCount()
        {  return ++__count;  }
      
        public static int Add(int operand1, int operand2)
        {  return operand1 + operand2; }
      
        // needed for testability
        internal static void ResetCount()
        {
           __count = 0;
        }
      }
      
      ...
      
      //test1
      MyStaticClass.Add(2,3);        // => 5
      MyStaticClass.GetAddCount();    // => 1
      
      // test2
      MyStaticClass.Add(2,3);  // => 5
      //MyStaticClass.ResetCount(); // needed for tests
      MyStaticClass.GetAddCount();  // => unless Reset is done, it can differ from 1
      

      【讨论】:

        猜你喜欢
        • 2011-08-23
        • 2019-05-11
        • 2019-02-03
        • 1970-01-01
        • 2022-01-02
        • 2018-08-23
        • 1970-01-01
        • 1970-01-01
        • 2023-04-01
        相关资源
        最近更新 更多