TDD:帮助编写Testable类

时间:2009-01-23 17:00:15

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

我有一个快速的小应用程序,并想尝试使用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”都是非空时才应该为真。所以我想测试它:

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

这很容易,但允许公共setter和无参数构造函数似乎是一个糟糕的折衷方案。所以另一种方法是在构造函数中输入'purchaseOrderText'值,但之后我也在测试'GetNewNumber'和'GetNewLine'的代码。

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

5 个答案:

答案 0 :(得分:8)

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

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

答案 1 :(得分:2)

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

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

[assembly: InternalsVisibleTo( "UnitTests" )]

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

答案 2 :(得分:1)

在测试项目中为您的类创建一个私有访问器,然后使用访问器设置测试的属性。在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);
}

答案 3 :(得分:0)

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

答案 4 :(得分:0)

有点偏离主题:)

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

只是我的2美分......