【问题标题】:TDD: Help with writing Testable ClassTDD:帮助编写可测试类
【发布时间】:2009-01-23 17:00:14
【问题描述】:

我有一个快速的小应用程序,想尝试使用 TDD 进行开发。在我找到 ASP.NET-MVC 之前,我从未使用过 TDD,实际上我什至不知道它是什么。 (我的第一个 MVC 应用程序有单元测试,但它们很脆弱、耦合方式、需要太多的维护,并且被放弃了——我来学习单元测试!= TDD)。

应用背景:

我有一个以字符串形式读取的采购订单的文本转储。我需要解析文本并返回新的采购订单号、新的行项目号、旧的采购订单号、旧的采购订单行号。很简单。

现在我只处理新的采购订单详细信息(编号/行)并且有这样的模型:

public class PurchaseOrder
{
    public string NewNumber {get; private set;}
    public string NewLine {get; private set;}

    new public PurchaseOrder(string purchaseOrderText)
    {
        NewNumber = GetNewNumber(purchaseOrderText);
        NewLine = GetNewLine(purchaseOrderText);
    }

    // ... definition of GetNewNumber / GetNewLine ...
    //  both return null if they can't parse the text
}

现在我想添加一个方法“IsValid”,该方法只有在“NewNumber”和“NewLine”都为非空时才为真。所以我想像这样测试它:

public void Purchase_Order_Is_Valid_When_New_Purchase_Order_Number_And_Line_Number_Are_Not_Null()
{
    PurchaseOrder order = new PurchaseOrder()
    {
        NewNumber = "123456",
        NewLine = "001"
    };

    Assert.IsTrue(order.IsValid);
}

这很容易,但允许公共设置器和无参数构造器似乎是一个糟糕的折衷方案。因此,另一种方法是在构造函数中输入“purchaseOrderText”值,然后我也在测试“GetNewNumber”和“GetNewLine”的代码。

对于如何将其编写为可测试的类,同时试图将其锁定在对模型有意义的方面,我有点难过。这似乎是一个常见问题,所以我想我只是缺少一个明显的概念。

【问题讨论】:

    标签: c# asp.net-mvc unit-testing tdd


    【解决方案1】:

    一种解决方案是不让构造函数完成工作:

    public class PurchaseOrder
    {
        public PurchaseOrder(string newNumber, string newLine)
        {
            NewNumber = newNumber;
            NewLine = newLine;
        }
        // ...
    }
    

    那么测试就变得简单而独立了——您不会同时测试GetNewNumberGetNewLine

    为了帮助使用PurchaseOrder,您可以创建一个工厂方法将其组合在一起:

    public static PurchaseOrder CreatePurchaseOrder(string purchaseOrderText)
    {
       return new PurchaseOrder(
         GetNewNumber(purchaseOrderText),
         GetNewLine(purchaseOrderText));
    }
    

    【讨论】:

    • 这也是对单一职责原则的更好利用——从文本到数字和行项目的转换本质上是一个 IO 功能,而不是采购订单固有的功能。如果您的文本转储格式发生变化,您的采购订单应该不需要。
    • 我要补充一点,这正是 TDD 擅长揭示的那种设计实现——如果你不能对某件事进行合理的测试,它可能做得太多了。跨度>
    • 感谢您的回答和 cmets。我同意这两者,并且必须承认看到 TDD 这样做我有点头晕(像宣传的那样工作)。这个程序非常简单,可以像 main 中的 1 个程序函数一样完成,但我认为你必须边做边学,而不是仅仅阅读它。
    【解决方案2】:

    不要将设置器公开,而是将它们设置为内部,然后在您的主项目中创建您的测试程序集 InternalsVisibleTo。这样一来,您的测试可以看到您的内部成员,但其他人无法看到。

    在你的主项目中,放这样的东西;

    [assembly: InternalsVisibleTo( "UnitTests" )]
    

    UnitTests 是测试程序集的名称。

    【讨论】:

      【解决方案3】:

      在测试项目中为您的类创建一个私有访问器,然后使用该访问器为您的测试设置属性。在 VS 中,您可以通过在类中右键单击,选择 Create Private Accessor,然后选择测试项目来创建私有访问器。在您的测试中,您可以像这样使用它:

      public void NameOfTest()
      {
          PurchaseOrder_Accessor order = new PurchaseOrder_Accessor();
          order.NewNumber = "123456";
          order.NewLine = "001";
          Assert.IsTrue(order.IsValid);
      }
      

      如果你有一个默认的构造函数,你可以这样做:

      public void NameOfTest()
      {
          PurchaseOrder order = new PurchaseOrder()
          PurchaseOrder_Accessor accessor =
                      new PurchaseOrder_Accessor( new PrivateObject(order) );
          accessor.NewNumber = "123456";
          accessor.NewLine = "001";
          Assert.IsTrue(order.IsValid);
      }
      

      【讨论】:

      • 哇,我什至不知道访问器。找到关于他们的 MSDN 文章,它在我的待办事项列表中 :) 谢谢!
      【解决方案4】:

      我可能会创建一个新的构造函数 public PurchaseOrder(string newNumber, string newLine)。 IMO,无论如何你都可能需要它。

      【讨论】:

        【解决方案5】:

        有点离题:)

        使用 TDD,首先要编写单元测试,然后编写足够的代码以通过测试,然后再进行重构。通过首先编写测试,您应该确切地知道什么以及如何编程以使您的代码可测试!

        只是我的 2 美分...

        【讨论】:

        • 我正在尝试这个。我的下一个测试是 IsValid 测试,这是一个易于编写的测试。在我写完测试之后,很明显我的代码无法支持测试,所以我要么需要重构,要么重写测试(但我不知道是哪个)。谢谢!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-08-26
        • 2011-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-12
        • 1970-01-01
        相关资源
        最近更新 更多