Test-Driven

by Alex Chaffee

alexch @ gmail.com

Intended Audience

Part I: Basic Techniques

Red, Green, Refactor

Make it green, then make it clean

Make it green

Addicted to green

Blueprint for a Single Test

Assert

One Step At A Time

The Null Test

Test List

Fake it till you make it

Assert First

Fake It 'Til You Make It

Obvious Implementation

Interlude: The Blank Page

Part II: Testing Philosophy

Automated Testing Layers

A Good Test Is...

Tests are "Executable Specifications"

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." – Martin Fowler

Why do you test?

Why do you test?

When do you test?

When do you test?

When do you test?

Why test first?

Why test first? (cont.)

Can't I write tests later?

How can you write tests for code that doesn't exist?

Some tricks to get started:

"If you can't write a test, then you don't know what the code should do. And what business do you have writing code in the first place when you can't say what it's supposed to do?" - Rob Mee

Spike to Learn

If you don't know what test to write, then start with a spike.

A "spike" is an experiment, a proof of concept, a playground.

Code until you feel confident that you understand the general shape of the solution.

Then put your spike aside and write it again test-first.

Unit Testing Is Hard...

...but it makes your life easier

Test-Driving Is Slower At First

TDD vs TDD

Quite a lot of overlap, but worth keeping difference in mind

Test for "essential complexity"

Tests Are An Extension of Code

Meszaros' Principles of Test Automation

Part III: Advanced Techniques

What to test?

Test everything that could possibly break

How much to test?

Triangulate To Abstraction

Step one:

function testSum() {
  assertEquals(4, plus(3,1));
}
plus(x, y) {
  return 4;
}

Step two:

function testSum() {
  assertEquals(4, plus(3,1));
  assertEquals(5, plus(3,2));
}
function plus(x, y) {
  return x + y;
}

Full Range Testing

Positive Tests

Negative Tests

Boundary Conditions

Descriptive Test Naming

nested "describe" blocks can help too...(see later slide)

Should Statements

Nested Describe Blocks

describe('Set', ()=> {
  let set;
  describe('when first created', ()=> {
    beforeEach(()=> {
      set = new Set();
    });

    it('should exist', ()=>{
      expect(set).toBeTruthy();
    })

    it('should be empty', ()=> {
      expect(set.size()).toBe(0);
    });
  });
});

Output:

  Set
    when first created
      ✓ should exist
      ✓ should be empty

Test-Only Methods

Refactoring Test Code

Refactoring Test Code - How?

Evident Data

Increase test readability by clarifying your input and output values

assertEquals(86400, new Day().getSeconds())

vs.

assertEquals(60 * 60 * 24, new Day().getSeconds())

vs.

secondsPerMinute = 60
minutesPerHour = 60
hoursPerDay = 24
assertEquals(secondsPerMinute * minutesPerHour * hoursPerDay,
  new Day().getSeconds())

Matrix Tests

%w(a e i o u).each do |letter|
  it "#{letter} is a vowel" do
    assertTrue(letter.vowel?)
  end
end

Characterization Tests

How to Test Exceptions?

public void testUnknownCountry() {
  try {
    currencyConverter.getRate("Snozistan");
    fail("Should have thrown an exception for unknown country");
  } catch (UnknownCountryException e) {
    // ok
  }
}

The empty catch block is fine here, since here an exception is a success, not a failure to be handled.

Jasmine has a built-in way to test exceptions:

expect( function(){ parser.parse(bogus); } )
    .toThrow(new Error("Parsing is not possible"));

Characterization Tests

Pair Programming

Ping-Pong Pairing

Regression Test

"Regression tests are tests that you would have written originally." - Kent Beck

Do Over

Leave One For Tomorrow

The Need For Speed

Continuous Integration

Retrofitting

Q: What to do when you have an existing untested codebase?

A: Start small!

Fixtures and Factories

Test Doubles (Mock Objects)

A Test Double replaces the "real" instance of an object used by the production code with something suitable for the currently running test, but with the same interface.

Test Doubles in general are often called Mock Objects, but there's also a specific technical type of double called a Mock.

Test Doubles

Test Doubles (cont.)

Mock Clock

A very useful test double

In ruby:

@fake_time = Time.now
Time.stub(:now) { @fake_time }

In Jasmine (built in, see the docs for more details):

  it("causes a timeout to be called synchronously", function() {
    let timerCallback = jasmine.createSpy("timerCallback");

    setTimeout(function() {
      timerCallback();
    }, 100);

    expect(timerCallback).not.toHaveBeenCalled();

    jasmine.clock().tick(101);

    expect(timerCallback).toHaveBeenCalled();
  });

Complete Construction

BDD (specs)

Outside-in vs. Inside-out

Inside-out

Outside-in

Outside-in design, inside-out development

Part IV: Q&A

Thanks!

 Previous Lesson Next Lesson 

Outline

[menu]

/