Thursday, September 26, 2013

"But what if I had to test this?"

It's a fact that developing software using test driven development (TDD) techniques assures a higher quality than other conventional techniques.

TDD consists of making the developer start off with a test, and then write code to make the test pass, repeating this process in cycles. This makes us think about the best design for the smallest unit in code: methods.

Since you have to write the code that consumes your method before writing the actual method, you'll always end up with a simplistic, easy-to-use version of it. It's like putting ahead what you expect from your code, and then writing it. This often tends to push you to the best design: smaller, clearer methods with a single responsibility, fewer lines of code.

But forget about TDD for now, I'll blog about that later.

The message I'm trying to pass here is not "use TDD" or "write unit tests", but at least pretend that you're doing so! - By the way, you should totally experiment with TDD and PLEASE, DO WRITE UNIT TESTS!

But what if I had to test this?

Those of you who use TDD or just write unit tests will, eventually, change your design to make the testing easier.

But those of you who don't do any of them, you should.

Whenever writing a big fat method with lots and lots of lines of code, think about it. Ask yourself the question: "How would I test this?".

For example, take the following method:

public boolean DoSomething()
    var result = from child in this.Father.Children
                 from grandChild in child.GrandChildren
                 where (child.Foo == "Foo" &&
                       grandChild.Bar == "Bar" &&
                       child.Value > 10 &&
                       child.Value < 50 || 
                       grandChild.Amount * 100 < 0) ||
                       (child.Value > 100 &&
                       grandChild.Amount * 765 < 10000 &&
                       (child.Foo == "Not Foo" ||
                       ((int)grandChild.Amount ^ 2) == 3600))
                 group child by new { child.Foo, child.Value } into g
                 select new
                         Sum = g.Sum(child => child.Value),
                         FooMax = g.Max(child => child.Foo)

    var firstGroupedChild = result.First();

    if (firstGroupedChild.FooMax != "ZZZ")
        var correctedGrandChild = new GrandChild();

        correctedGrandChild.Bar = "Corrected grand child";
        correctedGrandChild.Amount += firstGroupedChild.Sum * 1.10;



        return true;

    return false;
I couldn't even think of a bad name to that!

Now ask yourself: "How do I test this?".
A: Man, you're gonna have a bad time...

If the developer who wrote that code had the exact same question in mind from the very beginning, I'm pretty sure he would have dropped the idea of doing so much stuff - including a complex Linq query - on a single method just right there. Imagine how many Unit Tests you would have to write just to test the minimum of something like that. That is a terribly poorly written method.

If you don't test at all, it's actually acceptable to have something like this, given that you're not too worried about the quality of your product anyway. But you should! Do care about quality! Even if you don't assure it, at least remember that it exists!

Also, be a good guy and think about the fellow developer who maintains this code. He'll sure have a terrible time trying to find out just why the hell one of the "child" objects was not found on the first query, and not summed up.

Now imagine if we could translate the method above to the following:

var children = Father.GetChildrenThatSatisfySomeCondition();

var groupedResults = Child.GroupBySomeCondition(children);

var childFooMax = groupedResults.Max(child => child.Foo);

if (childFooMax.Foo!= "ZZZ")
    var correctedGrandChild = GrandChild.GetNewCorrectedFromChild(childFooMax);
    DataAccessObject.PerformChildCorrectionWithGrandChild(childFooMax, correctedGrandChild);

    return true;

return false;

Ooooooh! It all makes sense now! Now I can even understand the logic flow of the things I have to test! I could totally write Unit Tests for each one of the extracted methods now, because they all seem to be small, cohesive, and have a single responsibility.

And that would have been the first choice of design if the developer had thought about testing it before, or while he was writing it.

Code quality - and specially the semantics - matter, always.

No comments:

Post a Comment