Sunday, 30 December 2012

Test Driven Development - objections, part 2

Welcome to part 2 of my post about TDD objections.

By the way, I found an interesting analysis on slideshare on the same topic. It's an interesting read, so be sure to take a look.

Where did we leave the last time?

Ok, let's take on the next set of objections from the list:

  1. Not enough time
  2. TDD will slow us down, because we'll have to create a lot of additional code
  3. TDD will slow us down, because we'll have to maintain a lot of additional code
  4. We don't have the necessary skills or experience
  5. We don't have the necessary tools
  6. (This one is unit testing specific) There are already other kinds of tests, we don't need unit tests?
  7. It's very hard to perform with legacy code
  8. I already know how to write a testable code, TDD will not really improve my design
  9. We've got enough quality and we're doing fine without TDD
  10. My manager won't let me do TDD
  11. There's no "scientific proof" and enough research on whether TDD really provides a return of investment

The ones I'm gonna talk about today are the one marked with strong text. Let's go then!

4. We don't have the necessary skills or experience

This is a valid impediment. However, its a common one in any skill. You see, it's an extremely rare situation that someone is proficient using a technique for the first time (think of the times when you learned how to use a keyboard and a mouse). Most techniques require some time and knowledge to master - the same is with TDD. Let's say that TDD is like a flower, where the core is the "Red-Green-Refactor" cycle and there are a lot of petals - good practices and heuristics. These include: need-driven design, triangulation, mocking, listening to tests etc.

Thankfully, lack of skills can be dealt with by providing training, mentoring, books, doing katas, randoris and by the teams drawing conclusions out of their own experience. In other words, this is a temporary obstacle. Of course, together with staff rotation in your team comes the need to renew part of the investment in skills, knowledge and experience.

5. We don't have the necessary tools

This too is a valid impediment. TDD is a kind of process that can be made a lot easier with tools. There are many areas where tools can improve the overall performance with TDD. These include:

  1. Code navigation
  2. Running unit tests
  3. Automated refactoring
  4. Code Analysis
  5. Quick fixes (like "generate method signature from its use)
  6. Continuous Testing
  7. Code generation

...etc.

If the issue is "money related" (e.g. there are tools on the market, but hey have to be bought), then the management should consider buying the best tools that are on the market and are available within the company budget. Thus, it's always good to have management buy-in for TDD. Thankfully, most of these tools (excluding the ones related to continuous testing) give a performance boost regardless of whether someone is using TDD or not, so many teams have such tools anyway.

If the issue is "technology related" (there are no tools on the market that work well with the technology your team is using), the good news is that TDD is usable even without these tools and still provides a lot of its benefits, since it is about analysis, design and specification. The tools only (well, "only" :-)) help in keeping focus and go faster through the whole cycle.

6. (This one is unit testing specific) There are already other kinds of tests, we don't need unit tests?

Although TDD is not solely limited to unit-level specs/tests, they form a crucial part of it. While some people believe that we can skip higher level specs in TDD, there's no one I know who says you can skip unit level.

Anyway, let's get to the point. this objection is based on a premise that TDD is about testing and "unit tests" produced with it are merely another kinds of tests. When I hear people raising this argument, they talk a lot about coverage, defect discovery, strengthening the testing net etc. Such arguments, for the most part, miss the point, since they ignore the biggest benefits of TDD which do not lie in its testing aspect (which I already discussed).

Dealing with such objections is really hard, because it requires others to accept a different point of view than the one they kept believing in so far. Sure, the books and authorities are on your side, but hey, the guys that read books and listen to authorities don't usually need convincing! So what to do in such case?

Remember, this argument is usually raised in teams that don't even do unit testing, let alone TDD. The following options are available:

  1. Point the opponents to the blog posts and literature - be careful with this. If you do it wrong, the other side may take it as questioning their professionalism. Also, they may just question the authorities you believe in - this is rather easy in software engineering world - they can just say "that doesn't convince me at all" and the game is over. You have to somehow show that you're passionate about TDD and point at such sources as to something that made you passionate. In other words, if you want to lead someone out of their biases, tell them that you too were led out before them and tell them how. This leaves them in a position of "look, there's a way to do things better" instead of "you're all idiots and only I know the truth". I'm hardly an expert on this matter, however, there are some "change patterns" that are worth checking out.
  2. Talk about your experiences from your previous projects if you have any or bring someone else if you don't. There are not many arguments as convincing as "I have experienced it myself", especially to the guys that don't have any experience in a certain field and are in a process of evaluating whether it makes sense to enter this field. Also, some people are more convinced by "soft" arguments ("we did it like this before and everybody in the team said that they feel in improvement in their coding style as never before"), while others are better convinced by "hard" arguments ("we did it like this before and we were able to get the best results of code complexity analysis in the whole project plus we were able to demonstrate a print out of documentation of 40 pages that just came out for free as we did TDD."). Also, better than words are living proofs ("you can just ask those guys I worked with and they show you what our living documentation looks like" or "Just ask the guys that were there with me on how they feel about it").
  3. Create an internal training. This has one big advantage and two small disadvantages. The advantage is that you have a lot of time (at least longer than on regular meetings) to lead people by hand through your argumentation, reasons, examples and so on. In other words, you're given a chance to give more full explanation of the idea. The first disadvantage is that usually you get to prepare for such training in your free time (since management usually approves only spending time on giving the training, not on preparing it). The second disadvantage is that the people that you want to convince can simply ignore the invitation to the training and not attend at all or (if they're forced to attend) they can spend the whole training doing their stuff on their laptops.

7. It's very hard to perform with legacy code

The version of this objection that I find valid is: "It's harder to perform with legacy code". Why? Because TDD requires the code to have a quality called "testability". Legacy code doesn't usually have it. Thus, the code has to be refactored, risking breaking something that already works (if you know how to do this, then the risk is significantly lower, but there's still some risk). On the other hand, there is this temptation to just "insert a little if clause right in the middle of this mess and be done with".

Anyway, the strategy to deal with this objection is to somehow make is clear that it's either "we don't have the necessary skills" or "we don't have the necessary tools" objection, just dressed differently. In the first case, take a look at the following books:

  1. Refactoring by Martin Fowler
  2. Dealing Effectively With Legacy Code by Michael Feathers
  3. Behead Your Legacy Beast. Refactor and Restructure Relentlessly With The Mikado Method (free e-book) by Daniel Brolund and Ola Ellnestam

and in the second case, talk to your boss and make sure that your team has the tools it needs.

Part 3 is already published! Go ahead and read it!

In the meantime, I'll be happy to hear your comments. I'm not an oracle and would gladly learn more about what objections do people receive and how they dispel them.

Thursday, 27 December 2012

Test Driven Development - objections, part 1

Since you're reading this blog, you probably have your own, more or less informed, view on TDD. Maybe you already read a book or two, maybe you've got two (or twelve) years of TDD practice on your back, but maybe you've heard about TDD and about it being "cool" only recently and are merely striving to learn more? If that's the case you've probably got a lot of doubts on whether TDD is really beneficial or whether it will prove beneficial in the specific environment you happen to work in. You're eager to get started, but you wonder whether the time and effort spent on learning TDD will prove itself to be well spent.

During my adventure with TDD, I encountered many objections, either from myself (and they were dispelled by others) or from other engineers (these I tried to dispel myself). Today, I'd like to comment on some of those objections I met with when talking about TDD. In particular:

  1. Not enough time
  2. TDD will slow us down, because we'll have to create a lot of additional code
  3. TDD will slow us down, because we'll have to maintain a lot of additional code
  4. We don't have the necessary skills or experience
  5. We don't have the necessary tools
  6. (This one is unit testing specific) There are already other kinds of tests, we don't need unit tests?
  7. It's very hard to perform with legacy code
  8. I already know how to write a testable code, TDD will not really improve my design
  9. We've got enough quality and we're doing fine without TDD
  10. My manager won't let me do TDD
  11. There's no "scientific proof" and enough research on whether TDD really provides a return of investment

This post is around the first three points, marked with strong text. The later points will be discussed in part 2 of this post. Ok, let's go!

1. Not enough time

This is my favorite objection, just because dispelling it is so fun and easy. I usually approach this kind of objections with Gojko Adzic's argument on how to solve Not Enough Time. You see, "not enough time" is always a facade reason - it is there only to hide the true one. The only direct solution to "not enough time" is to "make more time", which, by the way, is impossible. Luckily, we can restate it into something solvable like "something prevents me from allocating time for TDD". The further actions depend on what is this "something". Maybe it's a boss that will punish their employees for not fulfilling the short term goals? Maybe it's lack of appropriate knowledge or training? Anyway, these issues can be dealt with. "Not enough time" can't.

2. TDD will slow us down, because we'll have to create a lot of additional code

This is actually a (partially) valid argument. TDD really does lead to creating more code, which costs time. However, this does not necessarily mean that the overall development process takes more time than not writing this additional code. This is because this "test code" is not being written just for the sake of "being written" :-). The act of writing and the code that is created as a result of this writing provide a great value to a single developer and an entire team.

TDD aids developers in the process of analysis. What would otherwise be a mental effort to make everything up inside your head, turns into concrete failing or impossible-to-write code. This generates questions that are usually better asked sooner than later. Thanks to this, there's no escape from facing uncertainty.

The case is different (at least for me) when developing without TDD. In this approach, I discovered that I tend to write what I know first, leaving what I don't know for later (in hope that maybe I'll learn something new along the way that will answer my questions and lower uncertainty). While the process of learning is surely valuable, the uncertainty must be embraced instead of being avoided. Getting away from answering some key questions and leaving them for later generates a lot of rework, usually at the end of iteration, where it's very dangerous.

When I do TDD and I encounter a behavior that I don't know how to specify, I go talk with stakeholders ("Hey, what should happen when someone creates a subscription for magazines already issued?"). Also, If someone asks me to clarify my question, I can show them the spec/test I'm trying to write for the behavior and say "look, this does not work" or "so the output should be what?". This way, I can get many issues if not solved, then at least on the table much sooner than in case of non-TDD coding. It helps me eliminate rework, save some time and make the whole process more predictable.

TDD also provides a big help when designing. It lets you do your design outside-in, beginning with the domain and its work-flows, not the reusable, versatile, framework-like, one-size-fits-all utility objects that end up having only 30% of its logic ever used. This way, TDD lets you avoid over-design (by the way, code reuse is a good thing, it's just that preliminary generalization is as dangerous as preliminary optimization).

On the other hand, by promoting a quality called "testability", it promotes loose coupling, high cohesion and small, focused, well encapsulated objects. I already did some posts on that, so I'm not gonna delve more into this topic here. Anyway, striving for high testability helps avoid under-design.

Another way to think about the process of doing TDD is that you're actually documenting your design, its assumptions, legal and illegal behaviors. Others will be able to read it and learn from it when they face the task of using your abstractions in a new context.

3. TDD will slow us down, because we'll have to maintain a lot of additional code

It's true that, when done wrong, TDD produces a mass of code that is hard to maintain. Refactoring becomes a pain, each added functionality breaks dozens of existing specs/tests and the teams seriously consider abandoning the practice.

This happens more often when the teams do not adopt TDD, but rather stick with unit tests and do it only for the sake of testing.

The truth is that TDD, when done right, helps avoid such situations. Also, this help is actually one of its goals! To achieve this, however, you need two things.

The first one is knowing TDD good practices that help you write only the specs/tests that you need, focus on behavior and discover interactions between different objects, limiting an impact of a change to a small number of specs/tests. I actually didn't write about it yet on my blog, but there are some other sources of information. Anyway, this issue is easily solvable by a combination of training, mentoring, books and experience.

the second thing is "listening to the tests", covered both by Steve Freeman & Nat Pryce (they call it Synaesthesia) and Amir Kolsky & Scott Bain (they call it Test Reflexology). The big idea is that difficulties in writing and maintaining specs/tests are a very much desired feedback on quality of your design (also make sure to look at James Grenning's post on TDD as a design rot radar).

In other words, as long as the design is good and you know how to write tests/specs, this whole objection is not a problem. Of course, there is still a code to maintain, but I found it to be an easy task.

Another thing to keep in mind is that by maintaining specs/tests, you're actually maintaining a living documentation on several levels (because TDD is not solely limited to unit level). Just think how much effort it takes to keep an NDoc/JDoc/Doxygen documentation up to date - and you never actually know whether such documentation speaks the truth after a year of maintenance. Things get better with tests/specs, which can be compared with the code just by running them, so the maintenance is easier.

Part 2 is already written! Read it!

Also, feel free to leave your comment. How do you deal with these objections when you encounter them? Do you have any patterns you can share?

Friday, 14 December 2012

Reusing code in Test Data Builders in C#

This is a C# version of a post I did a while ago. Hope it's of any use.

Test Data Builders

Sometimes, when writing unit or acceptance tests, it's a good idea to use Test Data Builder. For example, let's take a network frame that has two fields - one for source, one for destination. A builder for such frame could look like this:

public class FrameBuilder
{
  private string _source;
  private string _destination;

  public FrameBuilder Source(string newSource)
  {
    _source = newSource;
    return this;
  }

  public FrameBuilder Destination(string newDestination)
  {
    _destination = newDestination;
    return this;
  }

  public Frame Build()
  {
    var frame = new Frame()
    {
      Source = _source,
      Destination = _destination,
    };
    return frame;
  }
}

and it can be used like this:

var frame = new FrameBuilder().Source("A").Destination("B").Build();

The issue with Test Data Builder method reuse

The pattern is fairly easy, but things get complicated when we have a whole family of frames, each sharing the same set of fields. If we wanted to write a separate builder for each frame, we'd end up duplicating a lot of code. So another idea is inheritance. However, taking the naive approach gets us into some trouble. Let's see it in action:

public abstract class FrameBuilder
{
  protected string _source;
  protected string _destination;
  
  public FrameBuilder Source(string newSource)
  {
    _source = newSource;
    return this;
  }
  
  public FrameBuilder Destination(string newDestination)
  {
    _destination = newDestination;
    return this;
  }
  
  public abstract Frame Build();
};

public class AuthorizationFrameBuilder : FrameBuilder
{
  private string _password;
  public AuthorizationFrameBuilder Password(string newPassword)
  {
    _password = newPassword;
    return this;
  }
  
  public override Frame Build()
  {
    var authorizationFrame = new AuthorizationFrame()
    {
      Source = _source,
      Destination = _destination,
      Password = _password,
    };
    return authorizationFrame;
  }
}

The difficulty with this approach is that all calls to FrameBuilder methods return a reference to FrameBuilder, not an AuthorizationFrameBuilder, so we cannot use calls from the latter after calls from the first. E.g. we cannot make a chain like this:

new AuthorizationFrameBuilder().Source("b").Password("a").Build();
  
This is because Source() method returns FrameBuilder, which doesn't include a method called Password() at all. Such chains cause compile errors.

Generics to the rescue!

Fortuntely, there's a solution for this. Generics! Yes, they can help us here, but in order to do this, we have to use a trick that in C++ world is called "Curiously Recurring Template Pattern" (don't know if it has any name in the C# world). By using the trick, we'll force the FrameBuilder (superclass) methods to return reference to its subclass (AuthorizationFrameBuilder) - this will allow us to mix methods from FrameBuilder and AuthorizationFrameBuilder in any order in a chain, because each method, not matter which of the classes it is defined in, returns a reference to a subclass.

Let's see how this works out in the following example:

//thankfully, the following is legal :-)
public class FrameBuilder<T> where T : FrameBuilder<T>
{
  protected string _source;
  protected string _destination;

  public T Source(string newSource)
  {
    _source = newSource;
    return this as T;
  }
  
  public T Destination(string newDestination)
  {
    _destination = newDestination;
    return this as T;
  }
}

public class AuthorizationFrameBuilder 
  : FrameBuilder<AuthorizationFrameBuilder>
{
  string _password;

  public AuthorizationFrameBuilder Password(string password)
  {
    _password = password;
    return this;
  }
  
  public AuthorizationFrame Build()
  {
    var frame = new AuthorizationFrame()
    {
      Source = _source,
      Destination = _destination,
      Password = _password,
    };
    return frame;
  }
}

Note that in FrameBuilder, the this pointer is cast to its generic type, which happens to be the sublass on which the methods are actually called. this cast is identical in every method of FrameBuilder, so it can be captured inside a separate method or property like this:

  T This 
  {
    get { return this as T; }
  }
    
  public T Source(string newSource)
  {
    _source = newSource;
    return This;
  }

Summary

This solution makes it easy to reuse any number of methods in any number of different builders, so it's a real treasure when we've got many data structures that happen to share some common fields.

That's all for today - Hope you enjoy the C#-specific version of the solution. By the way, in contrast to C++, C# allows one or two solutions other than chained methods to achieve the same data-building result. Can you name them?