【问题标题】:TDD : Any pattern for constant testing?TDD:持续测试的任何模式?
【发布时间】:2009-07-06 19:18:54
【问题描述】:

常量是美丽的人 - 他们可以在一个独特的地方保存一个值,该值在您的代码中随处可见。更改该值只需要一个简单的修改。

生活很酷。

嗯,这就是承诺。现实有时会有所不同:

  • 您将LogCompleteFileName 常量值从L:\LOGS\MyApp.log 更改为\\Traces\App208.txt,您将获得两个文件:\\traces\App208.txt 用于跟踪,\\traces\App208.txt.log 用于日志...
  • 您将TransactionTimeout 从 2 分钟更改为 4 分钟,但 2 分钟后仍然出现超时(花费一天后,您发现您还必须更改 DBMS 的超时时间和事务管理器的超时时间。 ..)。
  • 您将SleepTimeInMinutes1 替换为10 并没有看到任何变化(大约一个小时后,您发现常量的名称具有误导性:粒度不是分钟而是毫秒... )。
  • 更微妙的是:您将 CompanyNameYahoo 更改为 Microsoft,但自动邮件警报仍会发送到 alert@yahoo.com...

创建一个常量是一个契约。您是在告诉您的读者,无论何时他们更改值,它仍然会按照他们认为应该的方式工作。

没什么。

当然,您需要测试自己没有误导读者。您必须确保默示合同是正确的。

您如何通过 TDD 实现这一目标?我只是坚持这一点。我可以测试常量(!)值更改的唯一方法是使该常量成为应用程序设置...我是否必须得出结论,当我认为该值可以并且将会改变时,应该避免使用 const 关键字?

您如何使用 TDD 测试(所谓的)常量?

提前非常感谢:)

【问题讨论】:

    标签: c# testing tdd constants


    【解决方案1】:

    我测试常量 (!) 值更改的唯一方法是将该常量设为应用程序设置

    对我来说,您在问题中列出的所有用途听起来都像是应用程序设置,而不是常量。常量是一个常量值,例如:

    const decimal LITERS_PER_HOGSHEAD = 238.480942392;
    

    编辑添加:希望这比我轻率的回答更有帮助。我通常创建一个 AppSettings 类。此类中的一些属性是从配置文件中提取的,一些是我不希望更改的设置,还有一些可能是常量。

    public class AppSettings
    {
        public const decimal GILLS_PER_HOMER = 1859.771248601;
    
        public string HelpdeskPhone
        {
            get { // pulled from config and cached at startup }
        }
    
        public int MaxNumberOfItemsInAComboBox
        {
            get { return 3; }
        }
    }
    

    【讨论】:

    • 我认为这可能是唯一可行的方法,最后。但有时,我创建常量是为了便于阅读,或者只是因为我认为值在可预见的将来不会改变,但它可能会改变!说: MaxNumberOfItemsInAComboBox... 我不愿意为这些值创建应用程序设置。但是我必须确保如果有人更改我的应用程序仍然可以工作...感谢您的回答:)
    • +1 用于常量名称。答案也是……但 GILLS_PER_HOMER 把它推到了边缘。
    • 嘿,有机会知道 Gill_Per_Homer 是什么意思吗?对不起,我是法国人,就像一堆 stackoverflower :)
    • 我正在为我的示例寻找一个愚蠢的单位转换,并找到了这个很棒的网站:unitconversion.org。吉尔和荷马是两个古老的体积计量单位。谢天谢地,法国人给了我们公制来取代这个烂摊子。
    • “一堆 stackoverFLOWER”。诗意。
    【解决方案2】:

    有两种常量:

    1) 方便/可读性的常量

    使用 TDD 编写代码时,生产代码的每一行都应该存在,因为首先需要编写该代码的测试失败。当你重构代码时,一些魔法值将被提升为常量。其中一些可能也可以作为应用程序设置,但为了方便(更少代码),它们已在代码中而不是外部配置文件中进行配置。

    在这种情况下,我编写测试的方式,生产代码和测试代码都将使用相同的常量。测试将指定常量按预期使用。但是测试不会重复常量的值,例如在“assert MAX_ITEMS == 4”中,因为那将是重复的代码。相反,测试将检查某些行为是否正确使用了常量。

    例如,here 是一个示例应用程序(由我编写),它将打印指定长度的 Longcat。如您所见,Longcat 被定义为一系列常量:

    public class Longcat {
    
        // Source: http://encyclopediadramatica.com/Longcat
    
        public static final String HEAD_LINES = "" +
                "    /\\___/\\         \n" +
                "   /       \\         \n" +
                "  |  #    # |         \n" +
                "  \\     @   |        \n" +
                "   \\   _|_ /         \n" +
                "   /       \\______   \n" +
                "  / _______ ___   \\  \n" +
                "  |_____   \\   \\__/ \n" +
                "   |    \\__/         \n";
        public static final String BODY_LINE = "" +
                "   |       |          \n";
        public static final String FEET_LINES = "" +
                "   /        \\        \n" +
                "  /   ____   \\       \n" +
                "  |  /    \\  |       \n" +
                "  | |      | |        \n" +
                " /  |      |  \\      \n" +
                " \\__/      \\__/     \n";
    ...
    

    测试验证常量使用正确,但不会重复常量的值。如果我改变一个常数的值,所有的测试都会自动使用新的值。 (而且 Longcat ASCII 艺术看起来是否正确,需要手动验证。尽管您甚至可以通过验收测试自动化,这对于更大的项目是值得推荐的。)

        public void test__Longcat_with_body_size_2() {
            Longcat longcat = factory.createLongcat(2);
            assertEquals(Longcat.BODY_LINE + Longcat.BODY_LINE, longcat.getBody());
        }
    
        public void test__Fully_assembled_longcat() {
            Longcat longcat = factory.createLongcat(2);
            assertEquals(Longcat.HEAD_LINES + longcat.getBody() + Longcat.FEET_LINES, longcat.toString());
        }
    

    2) 通用常量

    同样的应用程序也有一些永远不会改变的常数,例如米和英尺之间的比率。这些值可以/应该硬编码到测试中:

        public void test__Identity_conversion() {
            int feet1 = 10000;
            int feet2 = FEET.from(feet1, FEET);
            assertEquals(feet1, feet2);
        }
    
        public void test__Convert_feet_to_meters() {
            int feet = 10000;
            int meters = METERS.from(feet, FEET);
            assertEquals(3048, meters);
        }
    
        public void test__Convert_meters_to_feet() {
            int meters = 3048;
            int feet = FEET.from(meters, METERS);
            assertEquals(10000, feet);
        }
    

    生产代码如下:

    public enum LengthUnit {
    
        METERS("m", 1.0), FEET("ft", 0.3048), PETRONAS("petronas", 451.9), LINES("lines", 0.009);
    
        private final String name;
        private final double lengthInMeters;
    ...
    
        public int from(int length, LengthUnit unit) {
            return (int) (length * unit.lengthInMeters / this.lengthInMeters);
        }
    }
    

    请注意,我没有为Petronas Twin Towers 的高度编写任何测试,因为该信息是声明性的(声明性数据很少被破坏)并且我已经为转换逻辑编写了测试(见上文)。如果添加更多类似的常量(埃菲尔铁塔、帝国大厦等),它们将由应用程序自动定位并按预期工作。

    【讨论】:

      【解决方案3】:

      从我读到的你的问题来看,这与 TDD 无关。您描述的用法不是真正的常量,而是配置值,因此在这些情况下,您不应该使用 const 修饰符。

      【讨论】:

      • 好的,但是“真正的常量”呢?我们如何确保某个地方的某个人可以更改它们而无需测试所有内容?如果他或她必须测试所有内容,我们是否应该使用常数?没有常数,信息很明确:伙计,你不走运!您必须搜索所有源代码并测试所有内容。创建一个常量是一个契约,你如何确保它会正常?谢谢你的回答:)
      • 如果您使用 TDD,测试所有内容只需按一下按钮。一个常量首先不应该存在,除非首先有一个失败的测试需要创建该常量。如果在更改常量后运行测试时没有中断,则一切都按照测试指定的方式工作。
      • “真常数”?圆周率?它永远无法改变。常量或配置参数的测试是微不足道的。设计问题是您遇到的问题——人们没有按照您认为应该使用的方式设计使用配置参数的东西。那不是TDD。这就是设计。
      • S.Lott,你是对的。但是每次更改常数时,我仍然必须测试所有内容!我是一名软件维护人员(实际上),今天我花了 8 个小时试图弄清楚为什么改变一个简单的常量的值并没有改变任何东西!我的观点是:下次我将编写一个常量(或应用程序参数)时,我如何确保我的伙伴如果想更改它的值就不会失去这一天? TDD 非常酷,DRY 非常棒,但总而言之,让我们的同胞可以理解是最终目标。
      • Esko,你能在第一级回答同样的问题吗?这是我现在最喜欢的。
      【解决方案4】:

      那是因为所有这些东西都不是常数......实际上是:

      • G (6.67300 × 10-11 m3 kg-1 s-2)
      • c (299 792 458 m / s)
      • pi (3.1415926535897931)
      • L (6.0221415 × 1023 mol-1)
      • ...

      如果这些事情发生变化,请不要担心,您的应用程序崩溃将是最后一个重要的 xD

      【讨论】:

      • 我知道有人会注意到的! xD
      【解决方案5】:

      几件事。

      首先,TDD 和 TDD 提倡的紧急设计是分离你的职责,应用 DRY 和依赖注入。

      对 const 进行单元测试很容易,而且可能有点毫无意义。

      但在我看来,测试另一个单元对该 const 的评估不是 单元测试。这是一个集成测试。在其他单元中测试 const 值将由 mock 或 stub 覆盖。

      其次,您的示例多种多样:

      您的日志示例仅使用 1 个文件。只是存在2个。如果要求只存在一个文件,那么您可以为此制作一个测试。

      事务超时测试应由集成测试进行。它应该表明您的原始测试不是问题所在。

      公司名称的更改很好,因为它与公司名称有关。域名应该也可以是不同的常量。

      正如其他人提到的那样,在我周围传递一个配置类有助于在测试其他类时模拟/存根。

      【讨论】:

      • 非常感谢您的评论。但是我真的很糟糕(每隔几天)改变常量值......在我看来,如果你不能确保你的常量可以改变,你最好使用文字!然后我会更加怀疑,我会检查所有的代码。我花了这么多时间试图弄清楚为什么更改常量没有达到预期的效果......感谢您的回答。
      【解决方案6】:

      在我看来,您主要使用常量来说明配置设置。这是 ConfigurationManager 的理想选择,但也很难用于测试。

      我会推荐以下用法:

      const String SomeValue = "TESTCONSTANT";
      
      static class ConfigurationSettings
      {
          static String SomeProperty
          {
              get
              {
                 var result = SomeValue;
                 if (ConfigurationManager.AppSettings["SOMEKEY"] != null)
                     result = ConfigurationManager.AppSettings["SOMEKEY"];
                 return result;
              }
          }
      }
      

      【讨论】:

        【解决方案7】:

        如果您要进行单元测试,我认为这样的事情没有任何问题:

        [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
        public void DefaultValue_Equals_8()
        {
             Assert.AreEqual<int>(8, MyNamespace.MyClass.DefaultValue);
        }
        

        测试:

        namespace MyNamespace
        {
            public class MyClass
            {
                public const int DefaultValue = 8;
            }
        }
        

        这将检测常量是否已更改。

        你的问题的另一部分实际上是使用常量consistently。如果您没有以预期的方式在任何必要的地方使用它,那么您就不是依赖于常量,而是依赖于计算(或magic numbers)。

        【讨论】:

          【解决方案8】:

          其中一些示例是配置项而不是常量。在这种情况下 - 即使您在短期内使用常量,也应该将它们注入到使用它们的位置并且该代码正常测试。如果在运行时无法更改该值,那么您可以使用如下所述的围绕该值的示例测试。

          如果您要测试的东西确实是一个常数(例如const FEET_PER_METRE = 3.28084),那么只需一个包含计算和值的示例测试(该测试的数据应该来自独立来源,而不仅仅是计算它你自己从你声明的常量)。

          Assert.AreEqual<decimal>(29.5276, converter.metresToFeet(9))
          

          这样测试有两个目的:

          1) 如果事实证明您的示例和常量错误,那么您可以使用正确示例更新您的测试,获得一个失败的测试,然后将常量修复为绿色。

          2) 它提供回归安全性。如果有人不小心更改了常量(例如,更改了错误的值或在编辑文件中的其他内容时对值进行了粗手指操作),那么他们将得到一个失败的测试来警告他们。

          【讨论】:

            猜你喜欢
            • 2011-04-19
            • 2010-10-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-14
            • 2011-11-16
            • 2017-08-26
            相关资源
            最近更新 更多