Showing posts with label Test-Driven Development. Show all posts
Showing posts with label Test-Driven Development. Show all posts

Thursday, May 2, 2019

Introducing TDD to Your Organization

This blog post was originally published on the ThoroughTest website, back when that was a thing. As a co-founder and primary content contributor for ThoroughTest, I absolutely own the rights to this post and the source code to which it refers. I intend to reproduce each blog post here on my personal blog since the company is no longer in business.


We've heard from developers at several companies (large and small, publicly and privately held) that they're interested in implementing test-driven development, but they're not sure how to introduce the concept to their organizations. Below you'll find our preferred method for getting your fellow developers on board with you, then in our next post we'll explain how to convince management that it's a good idea.

First things first, you're a developer and you think test-driven development is the bee's knees, but your fellow developers don't. We need to get that out of the way right up front. If that's not you, then the approach we're about to provide may not be very helpful.

We've written before about some of the benefits of test-driven development and those are all good points to bring up to your fellow developers. But let's look at how we can target them a little more directly now. In our experience, all developers hate re-work as much as (or usually more than) any other part of their job so we're going to focus our pitch around re-work.

The first step in avoiding re-work is to really understand what you're building. It doesn't matter how rock solid your code is if you built the wrong thing in the first place. Test-driven development addresses this problem by forcing the developers to walk through the requirements of the product while they're writing their tests; before any code is written. During this phase developers can - and should - seek clarification from the product owner, which will lead to a product more in line with what the stakeholders want.

The next way we can avoid re-work is to reduce the number of defects in the code we do write. There are two ways test-driven development reduces the number of defects that make it to production. First, the fewer lines of code, the fewer opportunities exist for bad code and test-driven development reduces the number of lines of code by keeping the developer focused on delivering features that were actually requested. Second, when code is written using test-driven development there will be a complete suite of fully automated unit tests running at the end. Although this doesn't eliminate defects directly, it does ensure that what is written works as the developer expected. At this point we have code that works the way we expect it to, and does what the product owner wants it to.

The last step in avoiding re-work is actually part of the process of re-work. Despite our best efforts, most code will contain defects that get all the way to production and some developer down the road will need to fix them. Test-driven development protects those future developers by providing validation (through the complete automated test suite) that the bug fix doesn't introduce a new bug in a related area. However, test-driven development also has the added benefit of speeding up the bug fix process itself. When defects are reported from production they sometimes only exist under very particular circumstances that can be difficult to reproduce. When using test-driven development to fix a bug the first step is creating a test that proves the bug exists. Because our code is structured to facilitate testing we can more easily isolate reported defects. Once we've isolated the defect we can fix it, then run the test we already wrote to prove the bug is fixed. From there we can re-run the entire testing suite to make sure everything still works as expected and we're ready to move on from the defect and back to writing new code.

These are the reasons developers will be interested in trying out test-driven development, but you still need to create your argument in a way that gets their attention. We recommend something along the following lines:

"Using test-driven development will decrease re-work for all of us, giving us more time to focus on writing the cool new features we all like. The process can seem hard at first, but if we stick to it for six months we'll see fewer bugs making it to production, which will allow us to focus on more new features. Don't you hate having to switch gears in the middle of working on some cool new feature so you can go fix a bug in something you wrote a year ago, or even worse, something you didn't even write? Test-driven development can reduce the number of defects that make it to production AND allow us to more quickly fix the bugs that do make it out."

Most developers care about their code and they care about writing good code. Test-driven development is one more weapon they can include in their arsenal to write good code. You just have to help them see how test-driven development makes their lives easier and makes their jobs more fun. Hopefully, this approach will help you do that.

Wednesday, May 1, 2019

TDD from a Manager's Perspective

This blog post was originally published on the ThoroughTest website, back when that was a thing. As a co-founder and primary content contributor for ThoroughTest, I absolutely own the rights to this post and the source code to which it refers. I intend to reproduce each blog post here on my personal blog since the company is no longer in business. This post in particular was actually written by a guest writer, Adam Johnson. He was a great manager I worked for previously and he graciously accepted my request to have him write a post for us.


Test Driven Development(TDD) is hard. It is hard from a developer perspective and it can be even harder from a manager perspective. Not only does a manager need to help drive TDD within team members, they also need to ensure the management chain above them understands the benefits of TDD.

Helping a team strive for test driven development is a slow process. Each developer will progress at their own pace. There will definitely be push back about "how hard it is" and "why are you making us do this" but being consistent in the message is key. It needs to be a collaborative effort when working with your team, not a top down mandate even if it is perceived as much. It will take numerous peer/pair programming sessions to help nudge team members in the right direction. It is important that proper peer programming is done, not a solo driver like the TDD expert on the keyboard for the majority of the session. Having a TDD champion that is a team member also helps drive the message home with the team. Working in concert with the manager, they can help other team members start down the long process of doing test driven development.

The long process not only occurs within the team, those outside the team also need to understand it is a long process. It really starts with the team's manager. If that person doesn't buy in to the benefits of TDD, it will be a long uphill battle trying to drive TDD within the team. After the immediate manager's buy in, the product owner and product manager will be the next to convince of the benefits of TDD. For a new team starting out with TDD, this means a slower velocity than what they previously had. In fact, any kind of metrics used by the team will change as it will take time for new members to get TDD. This is where the manager of the team needs to push back. It requires a manager willing to take all the heat to ensure the team is producing quality code and continually improving as they go through this process. There will absolutely be questions about why the team slowed down or why did this release not have as many features but once that release goes out and the total cost of ownership goes down due to the increased quality, the outsiders will start buying in.

The development manager needs to be consistent in the message to the team and the message to those outside of the team. Whether you are 1 day from releasing or 100 days, that hard process will get easier with each passing day. By delivering a consistent message and understanding that it is a long and hard process, the benefits will soon come to light not only for the team but for the entire organization.

Tuesday, April 30, 2019

TDD in JavaScript (Part 3)

This blog post was originally published on the ThoroughTest website, back when that was a thing. As a co-founder and primary content contributor for ThoroughTest, I absolutely own the rights to this post and the source code to which it refers. I intend to reproduce each blog post here on my personal blog since the company is no longer in business.

In our twoprevious posts on this topic we setup Jasmine in preparation for simple browser-based tic-tac-toe game, established our requirements for the game, and wrote our first test and first bit of functionality for the game. In this post we're going to work quickly through the iterative process of test-driven development.

Before we write a bunch of tests we're going to introduce the beforeEach and afterEach functions of Jasmine. As their names describe beforeEach and afterEach are executed before and after each spec (test) in a describe block, respectively. We're going to migrate some pieces from our first test into these functions so we can reuse them.

Changing our tests to be more concise, reusable, or more efficient is part of the very important refactoring step in test-driven development.
describe('selectBox', () => {
  beforeEach(() => {
    const elementToCreate = document.createElement('button');
    elementToCreate.id = 'topLeft';
    elementToCreate.innerHTML = ' ';
    elementToCreate.onclick = selectBox(elementToCreate);
    document.body.appendChild(elementToCreate);
  });

  it('should mark the box with the current players marker', () => {
    // arrange
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(element.innerHTML).toBe('X');
  });

  afterEach(() => {
    document.body.removeChild(document.getElementById('topLeft'));
  });
});
Now that we've refactored our unit test we can quickly write some more. We recommend writing the unit test blocks before writing all of the tests. Doing so helps us identify potential discrepancies in the requirements and gives us a better understanding of what we're being asked to code.

describe('selectBox', () => {
  beforeEach(() => {
    const elementToCreate = document.createElement('button');
    elementToCreate.id = 'topLeft';
    elementToCreate.innerHTML = ' ';
    elementToCreate.onclick = selectBox(elementToCreate);
    document.body.appendChild(elementToCreate);
  });

  it('should mark the box with the current players marker', () => {
    // arrange
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(element.innerHTML).toBe('X');
  });
  
  it('should become Os turn when X marks a box', () => {});
 
  it('should mark the box with O when the current player is X', () => {});
  
  it('should become Xs turn when O marks a box', () => {});

  it('should end the game when O marks three boxes in a row', () => {});

  it('should end the game when X marks three boxes in a column', () => {});

  it('should end the game when O marks three boxes diagonally', () => {});

  it('should end the game when all boxes are marked and neither player has won',
    () => {});

  afterEach(() => {
    document.body.removeChild(document.getElementById('topLeft'));
  });
});
While we were writing our tests we noticed that our requirements never specified how to start a new game, whether to keep track of how many wins each player had or how many games ended in a draw. We're going to move forward without these features, but it's good that we identified them now. If the product owner really wanted them, we would have been able to renegotiate the work we're doing to make sure we included the most important features in the first release.

Now that we have our tests started we can start writing them. For the sake of brevity, we've written them all and included them here.

describe('selectBox', () => {
  beforeEach(() => {
    const elementToCreate = document.createElement('button');
    elementToCreate.id = 'topLeft';
    elementToCreate.innerHTML = ' ';
    elementToCreate.onclick = selectBox(elementToCreate);
    document.body.appendChild(elementToCreate);
  });

  it('should mark the box with X when the current player is O', () => {
    // arrange
    const element = document.getElementById('topLeft');

    // act
    element.click(element);

    // assert
    expect(element.innerHTML).toBe('X');
  });

  it('should become Os turn when X marks a box', () => {
    // arrange
    player = 'X';
    const element = document.getElementById('topLeft');

    // act
    element.click(element);

    // assert
    expect(player).toBe('O');
  });

  it('should mark the box with O when the current player is X', () => {
    // arrange
    player = 'O';
    const element = document.getElementById('topLeft');

    // act
    element.click(element);

    // assert
    expect(element.innerHTML).toBe('O');
  });

  it('should become Xs turn when O marks a box', () => {
    // arrange
    player = 'O';
    const element = document.getElementById('topLeft');

    // act
    element.click(element);

    // assert
    expect(player).toBe('X');
  });

  it('should end the game when O marks three boxes in a row', () => {
    // arrange
    player = 'O';
    moves = ['', 'O', 'O', 'X', 'X', '', 'X', '', ''];
    
    // act
    element.click(element);

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>O Wins!</h1>');
  });

  it('should end the game when X marks three boxes in a column', () => {
    // arrange
    player = 'X';
    moves = ['', 'O', 'O', 'X', 'X', '', 'X', '', ''];

    // act
    element.click(element);

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>X Wins!</h1>');
  });

  it('should end the game when O marks three boxes diagonally', () => {
    // arrange
    player = 'O';
    moves = ['', 'X', 'X', 'X', 'O', '', '', '', 'O'];

    // act
    element.click(element);

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>O Wins!</h1>');
  });

  it('should end the game when all boxes are marked and neither player has won',
    () => {
      // arrange
      player = 'X';
      moves = ['', 'X', 'O', 'O', 'O', 'X', 'X', 'O', 'X'];

      // act
      element.click(element);

      // assert
      expect(  
        document.getElementById('gameStatus')
        .innerHTML).toBe('<h1>Cat\'s Game!</h1>');
  });
  
  afterEach(() => {
    document.body.removeChild(document.getElementById('topLeft'));
  });
});
All of our tests fail except the first one, which is perfectly normal. Now we write just a little bit of code, using our tests as our guides.

let moves = ['', '', '', '', '', '', '', '', ''];
let player = 'X';

function selectBox() {
  this.innerHTML = player;

  if (this.id === 'topLeft') {
    moves[0] = player;
  } else if (this.id === 'topMiddle') {
    moves[1] = player;
  } else if (this.id === 'topRight') {
    moves[2] = player;
  } else if (this.id === 'centerLeft') {
    moves[3] = player;
  } else if (this.id === 'centerMiddle') {
    moves[4] = player;
  } else if (this.id === 'centerRight') {
    moves[5] = player;
  } else if (this.id === 'bottomLeft') {
    moves[6] = player;
  } else if (this.id === 'bottomMiddle') {
    moves[7] = player;
  } else if (this.id === 'bottomRight') {
    moves[8] = player;
  }

  if (moves[0] === moves[1] && moves[1] === moves[2]) {
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[0] === moves[3] && moves[3] === moves[6]) {
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[0] === moves[4] && moves[4] === moves[8]) {
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else {
    document.getElementById('gameStatus').innerHTML = '<h1>Cat\'s Game!</h1>';
  }

  player = player === 'X' ? 'O' : 'X';
}
This function causes all of our tests to pass, but doesn't mean the game will work. We're only checking whether the top row is a win, or the left column, or diagonally from top left to bottom right. If someone marks all three boxes in the middle row they won't win. We'll need to go back and write more tests, then update our code for the tests to pass.

We've added the additional tests we needed and we also refactored some of our test code to include some helper methods to create and remove all of the buttons from the form for us for each test.

describe('selectBox', () => {
  function createButton(id) {
    const elementToCreate = document.createElement('button');
    elementToCreate.id = id;
    elementToCreate.innerHTML = ' '
    elementToCreate.onclick = selectBox;
    document.body.appendChild(elementToCreate);
  }

  beforeEach(() => {
    createButton('topLeft');
    createButton('topMiddle');
    createButton('topRight');
    createButton('centerLeft');
    createButton('centerMiddle');
    createButton('centerRight');
    createButton('bottomLeft');
    createButton('bottomMiddle');
    createButton('bottomRight');

    const gameStatusElement = document.createElement('div');
    gameStatusElement.id = 'gameStatus';
    document.body.appendChild(gameStatusElement);
  });

  it('should mark the box with X when the current player is O', () => {
    // arrange
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(element.innerHTML).toBe('X');
  });

  it('should become Os turn when X marks a box', () => {
    // arrange
    player = 'X';
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(player).toBe('O');
  });

  it('should mark the box with O when the current player is X', () => {
    // arrange
    player = 'O';
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(element.innerHTML).toBe('O');
  });

  it('should become Xs turn when O marks a box', () => {
    // arrange
    player = 'O';
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(player).toBe('X');
  });

  it('should end the game when O marks three boxes in the top row', () => {
    // arrange
    player = 'O';
    moves = ['', 'O', 'O', 'X', 'X', '', 'X', '', ''];
    const element = document.getElementById('topLeft');
    
    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>O Wins!</h1>');
  });

  it('should end the game when X marks three boxes in the middle row', () => {
    // arrange
    player = 'X';
    moves = ['', 'O', 'O', 'X', 'X', '', 'X', 'O', ''];
    const element = document.getElementById('centerRight');
    
    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>X Wins!</h1>');
  });

  it('should end the game when O marks three boxes in the bottom row', () => {
    // arrange
    player = 'O';
    moves = ['', '', 'X', 'X', 'X', '', 'O', '', 'O'];
    const element = document.getElementById('bottomMiddle');
    
    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>O Wins!</h1>');
  });

  it('should end the game when X marks three boxes in the left column', () => {
    // arrange
    player = 'X';
    moves = ['', 'O', 'O', 'X', 'X', '', 'X', '', ''];
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>X Wins!</h1>');
  });

  it('should end the game when O marks three boxes in the middle column', () => {
    // arrange
    player = 'O';
    moves = ['X', 'O', '', 'X', '', 'X', '', 'O', ''];
    const element = document.getElementById('centerMiddle');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>O Wins!</h1>');
  });

  it('should end the game when X marks three boxes in the right column', () => {
    // arrange
    player = 'X';
    moves = ['O', 'X', 'X', 'O', '', 'X', '', 'O', ''];
    const element = document.getElementById('bottomRight');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>X Wins!</h1>');
  });

  it('should end the game when O marks three boxes diagonally from top left to bottom right', () => {
    // arrange
    player = 'O';
    moves = ['', 'X', 'X', 'X', 'O', '', '', '', 'O'];
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>O Wins!</h1>');
  });

  it('should end the game when X marks three boxes diagonally from top right to bottom left', () => {
    // arrange
    player = 'X';
    moves = ['O', 'O', 'X', '', 'X', '', '', 'X', 'O'];
    const element = document.getElementById('bottomLeft');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>X Wins!</h1>');
  });

  it('should not end the game when not all boxes are marked and neither player has won', () => {
    // arrange
    player = 'O';
    moves = ['', 'X', 'O', '', 'O', 'X', 'X', 'O', 'X'];
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('');
  });

  it('should end the game when all boxes are marked and neither player has won', () => {
    // arrange
    player = 'X';
    moves = ['', 'X', 'O', 'O', 'O', 'X', 'X', 'O', 'X'];
    const element = document.getElementById('topLeft');

    // act
    element.click();

    // assert
    expect(document.getElementById('gameStatus').innerHTML).toBe('<h1>Cat\'s Game!</h1>');
  });
  
  afterEach(() => {
    document.body.removeChild(document.getElementById('topLeft'));
    document.body.removeChild(document.getElementById('topMiddle'));
    document.body.removeChild(document.getElementById('topRight'));
    document.body.removeChild(document.getElementById('centerLeft'));
    document.body.removeChild(document.getElementById('centerMiddle'));
    document.body.removeChild(document.getElementById('centerRight'));
    document.body.removeChild(document.getElementById('bottomLeft'));
    document.body.removeChild(document.getElementById('bottomMiddle'));
    document.body.removeChild(document.getElementById('bottomRight'));
    document.body.removeChild(document.getElementById('gameStatus'));
  });
});
With these updated tests we'll need to update our code, so we've done that as well.

let moves = ['', '', '', '', '', '', '', '', ''];
let player = 'X';


function selectBox() {
  this.innerHTML = player;

  if (this.id === 'topLeft') {
    moves[0] = player;
  } else if (this.id === 'topMiddle') {
    moves[1] = player;
  } else if (this.id === 'topRight') {
    moves[2] = player;
  } else if (this.id === 'centerLeft') {
    moves[3] = player;
  } else if (this.id === 'centerMiddle') {
    moves[4] = player;
  } else if (this.id === 'centerRight') {
    moves[5] = player;
  } else if (this.id === 'bottomLeft') {
    moves[6] = player;
  } else if (this.id === 'bottomMiddle') {
    moves[7] = player;
  } else if (this.id === 'bottomRight') {
    moves[8] = player;
  }

  if (moves[0] === player && moves[1] === player && moves[2] === player) {
    // top row
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[3] === player && moves[4] === player && moves[5] === player) {
    // middle row
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[6] === player && moves[7] === player && moves[8] === player) {
    // bottom row
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[0] === player && moves[3] === player && moves[6] === player) {
    // left column
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[1] === player && moves[4] === player && moves[7] === player) {
    // middle column
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[2] === player && moves[5] === player && moves[8] === player) {
    // right column
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[0] === player && moves[4] === player && moves[8] === player) {
    // top left to bottom right
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (moves[2] === player && moves[4] === player && moves[6] === player) {
    // top right to bottom left
    document.getElementById('gameStatus').innerHTML = '<h1>' + player + ' Wins!</h1>';
  } else if (!!moves[0] && !!moves[1] && !!moves[2] &&
    !!moves[3] && !!moves[4] && !!moves[5] &&
    !!moves[6] && !!moves[7] && !!moves[8]) {
      document.getElementById('gameStatus').innerHTML = '<h1>Cat\'s Game!</h1>';
  }

  player = player === 'X' ? 'O' : 'X';
}
And now our game of Tic Tac Toe should work exactly as we planned. The only part left is creating the actual markup for the game board. Since that's not really TDD we're going to show you what we used, but not go into any great detail about it. Here's our HTML file.

<html>
 <head>
  <script src="./src/tic-tac-toe.js"></script>
  <style>
   #board {
    height: 50%;
    width: 100%;
    position: relative;
   }

   button {
    font-size: 3em;
    width: 75px;
    height: 75px;
    position: relative;
    float: left;
   }

   #centerLeft, #bottomLeft, #gameStatus {
    clear: left;
   }

   #gameStatus {
    height: 10%;
    text-align: center;
   }
  </style>
 </head>
 <body>
  <div id="board"></div>
  <div id="gameStatus"></div>
  <script>createForm()</script>
 </body>
</html>
We're creating the controls for the form dynamically so we'll show you that part, too (the createForm() function called at the bottom of the markup you see up there).
It's important to note that we skipped doing TDD on the createForm and createButton functions for this guide, but if we were really developing a game like this we absolutely would have.


let buttons = ['topLeft', 'topMiddle', 'topRight', 'centerLeft', 'centerMiddle', 'centerRight', 'bottomLeft', 'bottomMiddle', 'bottomRight'];

function createButton(id) {
  const button = document.createElement('button');
  button.innerHTML = ' ';
  button.onclick = selectBox;
  button.id = id;
  return button;
}

function createForm() {
  const board = document.getElementById('board');
  for (let i = 0; i < buttons.length; i++) {
    const button = createButton(buttons[i]);
    board.appendChild(button);
  }
}
These two functions are included in our tic-tac-toe.js file for easier inclusion in the page.

That's the end of the guide on using test-driven development for your JavaScript. Happy coding!


Monday, April 22, 2019

Changing your mindset for starting TDD

This blog post was originally published on the ThoroughTest website, back when that was a thing. As a co-founder and primary content contributor for ThoroughTest, I absolutely own the rights to this post and the source code to which it refers. I intend to reproduce each blog post here on my personal blog since the company is no longer in business.

Changing the patterns and practices of developers can be a lot like herding cats, especially if they have been following a personal standard for any length of time. It can seem that the harder you try to move people in one direction forcibly, the more resistance you will face. Not only will this resistance affect morale among a team, it will also create, hopefully, figurative claw marks and scratches all over the herder. The key to adoption for any new methodology is to show the overall benefits and have a transition plan. Changing an engrained mindset takes some time, but the results can be monumental and lead to a more productive and effective development team.

Test Driven Development (TDD) is a mindset. What exactly is a mindset? Well, a mindset is a set of beliefs, assumptions, and a perspective a person has about a topic. And, how exactly is TDD a mindset then? TDD is the perspective that it’s more important to understand a problem or requirement before finding or building a solution. This is characterized by writing unit tests before writing any code to satisfy those requirements.

Most developers have the belief that it’s easier to work on the solution first and then while doing so they will begin to understand the complexities of the problem. Is this belief wrong? No, but it’s generally not the most effective. One of the major goals of TDD is to become more effective in writing code and in that effectiveness quality will increase and bugs will decrease. This reason alone is why changing to a TDD mindset will be one of the more beneficial changes a developer can make in their career.

How many times have you or another developer written code weeks, months, or even years ago that maybe was not “clean” or that you even remember ever writing? That code has now become core to the application inadvertently. Today a new developer, or even yourself, makes a change to that code to solve a different requirement. The new code solves the new problem, but now an angry user calls the support desk and says that the feature they relied on for months has stopped working.

The cycle continues of writing code to solve a new problem, break an old feature, fix the old feature, and on and on it goes. This very concern is the solution a TDD mindset will solve. It solves the issue by the codebase always knowing what the desired outcome is for any line of code written because the developer had a clear understanding of the problem before ever writing functional code. Knowing the outcome and having a test written for it helps stop the perpetual cycle of “new code breaks old code” headlines that fill up release notes.

So, the question you’ll be having now is, how do you change to a TDD mindset? It’s all fairly straightforward and the first step is that when reading any requirement or learning of any new features or functionality needed is to ask one simple question: how do I test this? Another way to phrase it is: how do I know that the code I will write is going to satisfy the requirement accurately and completely? Write this question down on a sticky note and put it on however many monitors you have: “HOW DO I TEST THIS?” It cannot be stressed enough how important this question is when changing to a TDD mindset.

The next step for a TDD mindset shift is repetition. Repetition is key here. Asking yourself the above question over and over will help engrain the notion in your mind that understanding the outcome and impact of any requirement is way more important than finding a solution first. You wouldn’t casually build a house upon dirt and then later pour concrete under it, you pour the concrete first and then build the structure from blueprints. The idea is the same in TDD. Have a solid foundation of what needs to be accomplished before ever writing code.

The final step of changing your mindset to a TDD one is probably the most important. This is the step of implementation. It doesn’t do much good to only think about implementing the principles of a TDD mindset without putting them to actual use. It takes real practice to be able to implement anything new and TDD is no different. Without constant practice of getting a requirement, completely understanding it, writing unit tests, and then writing code, it will be very difficult to truly grasp the concept.

Adoption of TDD can be hard to overcome, but by just first thinking about how something can be tested before ever writing code you will have already taken the first step. At the very least start with that question in mind and slowly work your way towards the other steps in TDD. Through time and repetition, you will be well on your way to developing a new mindset that will result in more efficient code with fewer bugs.

Friday, September 6, 2013

Test-Driven Development

If you're like me, you've heard of Test-Driven Development (TDD) and you're kinda interested, but you haven't put a lot of time into figuring out how to do it.  Maybe you started down the path a long time ago and just got distracted (or bored, or you saw a squirrel).  Well, I recently had good cause to figure it all out and it turns out, it's really easy to learn.

The catch with learning TDD is that (if you're like me) you have to completely change the way you code against requirements.  No big deal, right?  So let me dive in and see if I can explain it simply for you (and remember that my blog is really more about creating an online repository of my own thoughts so I can figure out what I was thinking down the road).

TDD follows a pretty basic process that can be defined as: Red, Green, Refactor.  I prefer to refer to it as Fail, Pass, Clean Up, but that's just because I like to be contrary.  I find TDD easiest when I can identify acceptance criteria of my requirements.  Now, if you do agile you might be mentally protesting (or perhaps verbally, I don't know how unstable you are) that you're not supposed to have firm requirements in agile and that is sorta true, depending on your company's interpretation and implementation of agile.  But that's beside the point.  The point is that regardless of your development methodology you must have acceptance criteria.  Without acceptance criteria how will you know when you're finished coding and how will you know whether you've met your customers' needs?  So we identify acceptance criteria.  For simplicity, let's say our acceptance criteria is "the program displays the entered name".  Once you have your acceptance criteria, you can write your test.  Wait, what?!  Yeah, you create your test based on the acceptance criteria before you write any code at all.  That's why it's called Test-Driven Development.

[TestMethod]
public void ProgramShouldDisplayEnteredName()
{
    var p = new Program();
    var input = "Engineer_Andrew";
    var output = p.DisplayName(input);
    Assert.AreEqual("Engineer_Andrew", output);
}
That test fails, and that's a good thing.  The failed test is our "Red" part.  At this point, my program compiles, but the code doesn't actually do anything.  In fact, the code just throws an exception:

internal object DisplayName(string input)
{
    throw new NotImplementedException();
}

Now that we have a failing test (and we're proud of it) we write just enough code to make it turn "Green", or pass:

internal object DisplayName(string input)
{
    return input;
}

Since this test passes, we can move on to the third step; Refactor.  This is the part where we are going to refactor our code.  If you aren't familiar with that concept, it essentially means you're going to go back and make your code cleaner and/or more efficient and/or more extensible.  It can mean a lot of things, to be honest with you.  In this case it means we're going to do some validation of our input before we return it and we're going to return a string instead of an object:

internal object DisplayName(string input)
{
    if (string.IsNullOrEmpty(input))
    {
        return "Missing Name!";
    }
 
    return input;
}

The only thing left to do is re-run the same test as before and make sure it still passes.  It is also best practice to run all of the tests in your test suite to make sure you didn't inadvertently break something else.  As long as everything passes, you've met your acceptance criteria and you can move on to the next bit of code.

This is a seriously simplified example, but even on a larger scale the method provided here still holds.  Red, Green, Refactor (Fail, Pass, Clean Up).  That's all there is to it.