Test Oriented

Test Oriented

What are all the words we think about when we think of "testing"? You might be surprised what the research says we think about. Until recently most software engineers thought of time they didn't have, a problem they couldn't solve, and in general the concept raised anxiety levels. Why?

When we all learned to code, very few of us learned the art of Test Oriented Development. Heck, very few of us even learned patterned programming let alone orienting code for testing. So when it came time for testing the code, it was hard. Very hard. Hard enough that we usually left the code incompletely tested. It was hard because we had created code that had one or more classic no-no's for testing:

  • Heavy Dependencies
  • Complex Conditions
  • Multiple Responsibilities

Some concrete examples of these no-no's:

  • Database operations, UI prompts
  • Conditions with many predicates
  • Long methods, long classes

Writing tests for code like the above is time-consuming, breaks easily, is resource intensive, long executing, and in the end is Highly Unlikely to to completely cover the code. No wonder we throw up our hands...what would be the point??

Let's look at an example of such code and later in the post we will rewrite the code to be test oriented.!

namespace Common {
    public class HttpCommand {
        public virtual T Execute(Uri endpoint, string body, string contentType, NameValueCollection headers, string method, int timeout) {
            HttpWebRequest request = WebRequest.CreateHttp(endpoint);
            request.ContentType = contentType;
            request.Method = method;
            request.Timeout = timeout;
            request.Headers.Add(headers);
            if (!string.IsNullOrWhiteSpace(body)) {
                using(StreamWriter writer = new StreamWriter(request.GetRequestStream())) {
                    writer.Write(body);
                    writer.Flush();
                    writer.Close();
                }
            }
            using(HttpWebResponse response = request.GetResponse()) {
                using(StreamReader reader = new StreamReader(response.GetResponseStream())) {
                    return JsonConvert.DeserializeObject(reader.ReadToEnd());
                }
            }
        }
    }
}

This is what passes for really good code compared to the majority of code I have seen from my colleagues (reminder: that's 30+ years). Most software engineers look at this code and think it's short, lean, and done. The truth is, this is a wretched state to arrive at. Let's see why.

The first thing that we can point out is that this code has an HTTP request/response baked in. There is no way to get HttpCommand to avoid that call. What if the machine running HttpCommandFixture has HTTP port blocked? What if we have a large system with 10,000 unit tests and they all take as long as HttpCommand.Execute does? This is called a Heavy Dependency and it ain't good for software testing.

Another thing we see in this version of HttpCommand is that it depends on a third-party library (NewtonSoft.Json). This may not seem like much in this case, but coding like this leaves a testing weakness in your code. That could be the runtime expense of a library, or any number of things. Truth is...you don't know. And thus this also is not good for software testing.

The last thing that we see in HttpCommand is multiple responsibilities. Not only does Execute perform a request/response, but it also performs a JSON deserialization. In Right Syrup we pointed out that multiple responsibilities means multiple vectors of change - which means your tests are vulnerable to side effects. Again, this is not good for software testing.

Now HttpCommand is not a software system. It is one small class. Imagine a software system comprised of many, many such classes. This is the reality today. Even though XP, and TDD, and BDD, and Mock Frameworks, and IoC, and many other test-oriented things have been with us for 20 years - sad truth is that the majority of today's software is not test oriented. And why so many software projects fail.

Test Oriented

What if we coded with one thing in mind, SRP? And what if we added that no methods in a class are private? (Data of course must be private.) Let's see what that looks like, and how that orients the code for testing.

We know HttpCommand is supposed to make an HTTP request and serialize the response body into a generic type. Those are two responsibilities, so let's make it so that HttpCommand does the HTTP stuff and the serialization behavior is passed in by the caller. That should give us the following:

namespace Common {
    public class HttpCommand {
        public virtual T Execute(Uri endpoint, string body, string contentType, NameValueCollection headers, string method, int timeout) {
            HttpWebRequest request = WebRequest.CreateHttp(endpoint);
            request.ContentType = contentType;
            request.Method = method;
            request.Timeout = timeout;
            request.Headers.Add(headers);
            if (!string.IsNullOrWhiteSpace(body)) {
                using(StreamWriter writer = new StreamWriter(request.GetRequestStream())) {
                    writer.Write(body);
                    writer.Flush();
                    writer.Close();
                }
            }
            using(HttpWebResponse response = request.GetResponse()) {
                using(StreamReader reader = new StreamReader(response.GetResponseStream())) {
                    return JsonConvert.DeserializeObject(reader.ReadToEnd());
                }
            }
        }
    }
}

That wasn't a lot of work was it? Can you imagine thinking in this way to begin with? Yup, I'm sure. Not a lot of difference to HttpCommand - but a crucial payoff for testing. When it comes time for covering HttpCommand, you now have 50% less responsibilities to cover!

The other thing we should be thinking about HttpCommand, before we even start coding it is that any heavy dependency needs to be overridable. That means we are thinking we want to be able to make a Mock HTTP request/response. So instead of using WebRequest.CreateHttp directly, and instead of dealing with HttpWebRequest and HttpWebResponse directly, we want to use Interfaces. The last thing we should be thinking - should always be thinking - is SRP. This last thing is just one of those things you have to commit to. I can tell you what to do for HttpCommand - and even tell you the general Rules - but you have to choose to Commit. When you are coding the Execute method, and you have initialized the request object, you want to see those lines of code as Getting-the-Xxx - which gives you a method GetXxx (in this case GetRequest). Use this same technique for getting the response. You with me? If you follow SRP to its fullest - which automatically yields test-oriented code - then no method is more than 2-3 lines of code. Really. I'm serious.

So let's see what HttpCommand looks like now:

public class HttpCommand {
    public virtual T Execute(IHttpRequest requestInformation, IConverter converter) {
        using(IHttpResponse response = GetRequest(requestInformation).GetResponse()) {
            return converter.Deserialize(response.Body);
        }
    }
    protected virtual IHttpRequest GetRequest(IHttpRequest requestInformation) {
        return new HttpRequest(requestInformation);
    }
}

HttpCommand is now Test Oriented. When we go write the tests, we can override GetRequest and provide a Mock HTTP request. Similarly we can inject a Mock converter into Execute. The resulting tests are fast, resilient to change, and test individual responsibilities. And, if I am reading the tests in HttpCommandFixture, I quickly and easily get an understanding of what HttpCommand does. This is how the Secret Sauce (SRP) works. If you just commit to it, it takes care of you...everywhere. Runtime. Tests. Documentation. Change.

Test Driven Development

Those of you that have a committed TDD practice, awesome! That is how I worked for over a decade, and still if the situation is right for deriving code from tests then I do that. But if that is the only methodology you use, I encourage you to always be on the lookout of an even better way. What could be better than better?

TDD is a process that yields testable code. There are some rules that make up TDD, and when applied they result in a couple additional benefits (on top of testable code). But SRP is not at the center of TDD, and in fact SRP is not mentioned at all in TDD. This is one area where TDD can be improved upon. TDD can be superb for the beginner and intermediate test-oriented developer, but for the advanced test-oriented developer it is slower than what I call Test Oriented Development.

Test Oriented Development

In Test Oriented Development you place SRP at the center of every single thing you do. Every line, every symbol, every method body, every class, every everything is driven by SRP. On top of that substrate is a complete awareness of a few simple patterns and practices. Combined, you have a methodology that yields the complete coverage of TDD, singular responsibilities everywhere, and the practice itself is fast. Test Oriented Development is summarized as follows:

  • Use SRP Everywhere
  • Write Short Methods (5 lines maximum)
  • Minimize Method Arguments (3 maximum)
  • Only Public and Protected Methods, No Private
  • Overridable Behavior

You are ready to practice Test Oriented Development when you know you are ready. Until then I suggest using Test Driven Development and working on the patterns and practices I've laid out in this post. If you have any questions I am always available. Just hit me up @Rjae or @AppShapes on some social media.