C# TDD:编写可测试类的帮助

C# TDD:编写可测试类的帮助,c#,asp.net-mvc,unit-testing,tdd,C#,Asp.net Mvc,Unit Testing,Tdd,我有一个快速的小应用程序,想尝试使用TDD进行开发。我从未使用过TDD,事实上,直到我找到ASP.NET-MVC,我才知道它是什么。(我的第一个MVC应用程序有单元测试,但它们很脆弱,相互耦合,占用了太多的时间,被放弃了——我来学习单元测试!=TDD) 应用程序背景: 我有一个作为字符串读入的采购订单的文本转储。我需要解析文本并返回新采购订单号、新行项目号、旧采购订单号、旧采购订单行号。很简单 目前,我只处理新的采购订单详细信息(编号/行),并且有如下模型: public class Purch

我有一个快速的小应用程序,想尝试使用TDD进行开发。我从未使用过TDD,事实上,直到我找到ASP.NET-MVC,我才知道它是什么。(我的第一个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”都非null时才应该为true。所以我想像这样测试它:

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”的代码


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

与其公开设置器,不如将其内部化,然后在主项目中使您的测试程序集内部可见。这样,您的测试可以看到您的内部成员,但其他人无法看到

在你的主要项目中,放一些像这样的东西

[assembly: InternalsVisibleTo( "UnitTests" )]

其中,UnitTests是测试程序集的名称。

一种解决方案是不让构造函数执行此工作:

public class PurchaseOrder
{
    public PurchaseOrder(string newNumber, string newLine)
    {
        NewNumber = newNumber;
        NewLine = newLine;
    }
    // ...
}
这样,测试就很容易,而且是孤立的-您不需要同时测试
GetNewNumber
GetNewLine

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

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

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

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);
}

我可能会创建一个新的构造函数PublicPurchaseOrder(stringNewNumber,stringNewLine)。依我看,无论如何你都可能需要它。

有点离题:)

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


这也是对单一责任原则的更好运用——从文本到数字和行项目的转换本质上是一种IO功能,而不是采购订单固有的功能。如果您的文本转储格式发生变化,您的采购订单就不需要更改。我要补充的是,这正是TDD最擅长揭示的设计实现——如果您无法对某些内容进行合理的测试,它可能做得太多了。谢谢您的回答和评论。我同意这两种观点,并且必须承认,看到TDD这样做我有点头晕(正如广告所宣传的那样)。这个程序非常简单,可以像一个主要的程序函数一样完成,但我认为你必须通过实践来学习,而不是仅仅阅读。我正在尝试这个。我的下一个测试是IsValid,这是一个很容易编写的测试。在我编写测试之后,很明显,我的代码无法支持测试,所以我要么需要重构,要么重写测试(但我不知道是哪个)。谢谢哇,我甚至不知道访问者。找到了关于它们的MSDN文章,它在我的待办事项列表中:)谢谢!