Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TDD: Help with writing Testable Class

I have a quick little application and wanted to give a try to develop using TDD. I've never used TDD and actually didn't even know what it was until I found ASP.NET-MVC. (My first MVC app had unit tests, but they were brittle, way coupled, took too much up keep, and were abandoned -- I've come to learn unit tests != TDD).

Background on app:

I have a text dump of a purchase order read in as a string. I need to parse the text and return new purchase order number, new line item number, old purchase order number, old purchase order line number. Pretty simple.

Right now I'm only working on new purchase order details (number/line) and have a model like this:

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
}

Now I want to add a method "IsValid" that should only be true if "NewNumber" and "NewLine" are both non-null. So I want to test it like:

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

This is easy enough, but it seems like a bad compromise to allow public setters and a parameterless constructor. So the alternative is to feed in a 'purchaseOrderText' value in the constructor, but then I'm testing the code for 'GetNewNumber' and 'GetNewLine' as well.

I'm kind of stumped on how to write this as a testable class while trying to keep it locked up in terms of what makes sense for the model. This seems like it would be a common problem, so I'm thinking I'm just missing an obvious concept.

like image 791
anonymous Avatar asked Jan 23 '09 17:01

anonymous


2 Answers

One solution is to not have the constructor do the work:

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

Then testing is easy and isolated - you're not testing GetNewNumber and GetNewLine at the same time.

To help using PurchaseOrder you can create a factory method that puts it together:

public static PurchaseOrder CreatePurchaseOrder(string purchaseOrderText)
{
   return new PurchaseOrder(
     GetNewNumber(purchaseOrderText),
     GetNewLine(purchaseOrderText));
}
like image 139
orip Avatar answered Sep 29 '22 09:09

orip


Instead of making the setters public, make them internal and then make your test assembly InternalsVisibleTo in your main project. That way, your tests can see your internal members, but no-one else can.

In you main project, put something like this;

[assembly: InternalsVisibleTo( "UnitTests" )]

Where UnitTests is the name of your test assembly.

like image 37
Rob Prouse Avatar answered Sep 29 '22 09:09

Rob Prouse