Managers and Individual Contributors in Code

manager-individualContributorI’m lucky to work for a company that makes it possible to have a good technical career, so that I don’t have to become a manager just to get more money.

I like being an individual contributor because it gives me a sense of accomplishment when I get stuff done myself.

Anyway, I believe the distinction between manager and individual contributor is useful when writing code as well.

Let me show that using the example of Conway’s Game of Life.

Baby Steps

At code retreats, you’ll find people who start working on the problem from the bottom (cell) and those who start at the top (universe, world, grid, game, or whatever they call it).

Interestingly, both groups tend to run into a similar problem.

baby-stepsStarting at the bottom makes it easy to quickly implement the rules in a cell. But then the problem becomes how to connect the cells into a universe.

This often looks like too big of a step to properly test, e.g. with a new green test every two minutes.

Starting at the top makes sure we are not doing anything unnecessary or building any parts that would not connect properly. But here too it’s difficult to keep chipping away in baby steps.

The Single Responsibility Principle

To evolve a whole universe, at least these things need to happen:

  1. Iterate over all cells
  2. Determine each cell’s neighbors
  3. Count the number of alive neighbors
  4. Determine the cell’s next state based on its current state and the number of alive neighbors

Depending on some implementation choices (like keeping the universe immutable), there may be more work.

Yet the Single Responsibility Principle tells us that a class should have only one reason to change. You may count items #2 and #3 as one responsibility (or #3 and #4), but clearly there is more than one involved.

Manager Classes and Individual Contributor Classes and How to Test Them

Now remember the manager/individual contributor (IC) distinction. Managers manage ICs; that’s their whole job. We should count managing other classes as a responsibility too.

That would mean that a class either implements some logic (IC), or coordinates with other classes (manager), but not both.

mockI think this also ties into the state-based versus interaction-based testing debate: we should use state-based testing for IC classes and interaction-based testing (using mocks) for manager classes.

That would explain why both code retreat groups seem to get stuck: they have to switch from state-based to interaction-based testing and it seems that many developers don’t have a lot of experience with the latter.

Test-Driving Manager Classes

So let’s try interaction-based testing of a manager class. I’ll keep with the example of the Game of Life.

I’d like to begin with an architectural choice: I want the universe to be immutable, so that it would be easy to introduce multi-threading later. As the game evolves, I’ll have to create a new universe for every generation:

public class GameTest {
  @Test
  public void clonesImmutableUniverseWhenEvolving() {
    Universe universe = mock(Universe.class, "old");
    Universe nextGeneration = mock(Universe.class, "new");
    when(universe.clone()).thenReturn(nextGeneration);

    Game game = new Game(universe);
    assertSame("Initial", universe, game.universe());

    game.evolve();
    assertSame("Next generation", nextGeneration, 
      game.universe());
  }
}

I’m using Mockito to define how the Game interacts with the Universe. Mocking works best with interfaces, so Universe is an interface.

That means the game doesn’t know the type of universe and therefore can’t create a new instance of it. The universe provides a factory method clone() for this purpose.

If the universe is to be immutable, then its state should be provided in its constructor. This constructor is called by clone(), so that method needs the new state as its input.

What does that state look like? At this point we don’t know anything about the universe other than that it contains cells. Let’s keep it simple and provide the cells as input:

public class GameTest {
  @SuppressWarnings("unchecked")
  @Test
  public void clonesImmutableUniverseWhenEvolving() {
    Universe universe = mock(Universe.class, "old");
    Universe nextGeneration = mock(Universe.class, "new");
    when(universe.clone(any(Iterable.class)))
        .thenReturn(nextGeneration);

    Game game = new Game(universe);
    assertSame("Initial", universe, game.universe());

    game.evolve();
    assertSame("Next generation", nextGeneration, 
        game.universe());
  }
}

OK, so who is going to supply those cells to clone()? The game is a manager, so it should only manage, not provide logic. The universe is an IC that is responsible for the cells and their interconnections, so it already has a responsibility.

So it seems like we need a new type. The new type should be managed by the game, since that’s the only manager class we have. Thus we should add a new test in GameTest for this interaction.

The new type should be responsible for determining the new state of the universe from the old one. This is where the game’s rules come into play, so let’s call the new type Rules.

Let’s start with testing that the game manages the rules:

public class GameTest {
  @Test
  public void consultsRulesWhenEvolving() {
    Rules rules = mock(Rules.class);

    Game game = new Game(null, rules);
    assertSame("Rules", rules, game.rules());
  }
}

Now we want to test that evolve() consults the rules. Since the game is a manager, it can only request output from a collaborator and provide that as input to another collaborator. It shouldn’t contain any logic itself, since that would make it an IC.

The only other collaborator that the game currently has is the universe and that seems the correct candidate to provide the cells. But we need more than the cells to implement the rules: we also need to number of alive neighbors of each cell.

The universe is clearly the correct place to determine a cell’s neighbors. Should it also count the number of alive ones?

I guess it could, but I don’t particularly like that: the rules for Life use the number of alive neighbors, but it’s not hard to imagine a different set of rules that also depend on the number of dead neighbors. So I feel that the rules should do the counting.

This means that the input to the rules is a cell and its neighbors. Since Java doesn’t allow returning two pieces of information, we need to combine them. Let’s call the combination a neighborhood:

public class NeighborhoodTest {
  @Test
  public void holdsACellAndItsNeighbors() {
    Cell cell = mock(Cell.class, "cell");
    List<Cell> neighbors = Arrays.asList(
        mock(Cell.class, "neighbor1"), 
        mock(Cell.class, "neighbor2"));

    Neighborhood neighborhood = new Neighborhood(cell, 
        neighbors);

    assertSame("Cell", cell, neighborhood.cell());
    assertEquals("Neighbors", neighbors, 
        neighborhood.neighbors());
  }
}

Now we can make the universe return a neighborhood for each of the cells it contains and verify that those neighborhoods are used as input for the rules:

public class GameTest {
  @SuppressWarnings("unchecked")
  @Test
  public void clonesImmutableUniverseWhenEvolving() {
    Universe universe = mock(Universe.class, "old");
    Universe nextGeneration = mock(Universe.class, "new");
    when(universe.clone(any(Iterable.class)))
        .thenReturn(nextGeneration);
    when(universe.neighborhoods()).thenReturn(
        new ArrayList<Neighborhood>());

    Game game = new Game(universe, mock(Rules.class));
    assertSame("Initial", universe, game.universe());

    game.evolve();
    assertSame("Next generation", nextGeneration, 
        game.universe());
  }

  @Test
  public void consultsRulesWhenEvolving() {
    Universe universe = mock(Universe.class);
    Neighborhood neighborhood1 = new Neighborhood(
        mock(Cell.class), 
        Arrays.asList(mock(Cell.class)));
    Neighborhood neighborhood2 = new Neighborhood(
        mock(Cell.class), 
        Arrays.asList(mock(Cell.class)));
    when(universe.neighborhoods()).thenReturn(
        Arrays.asList(neighborhood1, neighborhood2));
    Rules rules = mock(Rules.class);

    Game game = new Game(universe, rules);
    assertSame("Rules", rules, game.rules());

    game.evolve();
    verify(rules).nextGeneration(neighborhood1);
    verify(rules).nextGeneration(neighborhood2);
  }
}

The next step is to make sure that the output from the rules is used to construct the new universe:

public class GameTest {
  @Test
  public void consultsRulesWhenEvolving() {
    Universe universe = mock(Universe.class);
    Neighborhood neighborhood1 = new Neighborhood(
        mock(Cell.class), 
        Arrays.asList(mock(Cell.class)));
    Neighborhood neighborhood2 = new Neighborhood(
        mock(Cell.class), 
        Arrays.asList(mock(Cell.class)));
    when(universe.neighborhoods()).thenReturn(
        Arrays.asList(neighborhood1, neighborhood2));
    Rules rules = mock(Rules.class);
    Cell cell1 = mock(Cell.class, "cell1");
    Cell cell2 = mock(Cell.class, "cell2");
    when(rules.nextGeneration(neighborhood1))
        .thenReturn(cell1);
    when(rules.nextGeneration(neighborhood2))
        .thenReturn(cell2);

    Game game = new Game(universe, rules);
    assertSame("Rules", rules, game.rules());

    game.evolve();
    verify(universe).clone(eq(
        Arrays.asList(cell1, cell2)));
  }
}

At this point we’re done with the Game class. All that is left to do, is create implementations for the three interfaces we introduced: Universe, Cell, and Rules. Each of these is an IC class, and thus fairly straightforward to test-drive using state-based testing.

Conclusion

I find that the distinction between manager and individual contributor classes helps me in deciding what the next test should be.

What do you think? Could this be part of the missing piece for a proper theory of Test-Driven Development?

On Measuring Code Coverage

In a previous post, I explained how to visualize what part of your code is covered by your tests.

This post explores two questions that are perhaps more important: why and what code coverage to measure.

Why We Measure Code Coverage

What does it mean for a statement to be covered by tests? Well, it means that the statement was executed while the tests ran, nothing more, nothing less.

We can’t automatically assume that the statement is tested, since the bare fact that a statement was executed doesn’t imply that the effects of that execution were verified by the tests.

If you practice Test-First Programming, then the tests are written before the code. A new statement is added to the code only to make a failing test pass. So with Test-First Programming, you know that each executed statement is also a tested statement.

If you don’t write your tests first, then all bets are off. Since Test-First Programming isn’t as popular as I think it should be, let’s assume for the remainder of this post that you’re not practicing it.

Then what good does it do us to know that a statement is executed?

Well, if the next statement is also executed, then we know that the first statement didn’t throw an exception.

That doesn’t help us much either, however. Most statements should not throw an exception, but some statements clearly should. So in general, we still don’t get a lot of value out of knowing that a statement is executed.

The true value of measuring code coverage is therefore not in the statements that are covered, but in the statements that are not covered! Any statement that is not executed while running the tests is surely not tested.

Uncovered code indicates that we’re missing tests.

What Code Coverage We Should Measure

Our next job is to figure out what tests are missing, so we can add them. How can we do that?

Since we’re measuring code coverage, we know the target of the missing tests, namely the statements that were not executed.

If some of those statements are in a single class, and you have unit tests for that class, it’s easy to see that those unit tests are incomplete.

Unit tests can definitely benefit from measuring code coverage.

What about acceptance tests? Some code can easily be related to a single feature, so in those cases we could add an acceptance test.

In general, however, the relationship between a single line of code and a feature is weak. Just think of all the code we re-use between features. So we shouldn’t expect to always be able to tell by looking at the code what acceptance test we’re missing.

It makes sense to measure code coverage for unit tests, but not so much for acceptance tests.

Code Coverage on Acceptance Tests Can Reveal Dead Code

One thing we can do by measuring code coverage on acceptance tests, is find dead code.

Dead code is code that is not executed, except perhaps by unit tests. It lives on in the code base like a zombie.

Dead code takes up space, but that’s not usually a big problem.

Some dead code can be detected by other means, like by your IDE. So all in all, it seems that we’re not gaining much by measuring code coverage for acceptance tests.

Code Coverage on Acceptance Tests May Be Dangerous

OK, so we don’t gain much by measuring coverage on acceptance tests. But no harm, no foul, right?

Well, that remains to be seen.

Some organizations impose targets for code coverage. Mindlessly following a rule is not a good idea, but, alas, such is often the way of big organizations. Anyway, an imposed number of, say, 75% line coverage may be achievable by executing only the acceptance tests.

So developers may have an incentive to focus their tests exclusively on acceptance tests.

This is not as it should be according to the Test Pyramid.

Acceptance tests are slower, and, especially when working through a GUI, may also be more brittle than unit tests.

Therefore, they usually don’t go much further than testing the happy path. While it’s great to know that all the units integrate well, the happy path is not where most bugs hide.

Some edge and error cases are very hard to write as automated acceptance tests. For instance, how do you test what happens when the network connection drops out?

These types of failures are much easier explored by unit tests, since you can use mock objects there.

The path of least resistance in your development process should lead developers to do the right thing. The right thing is to have most of the tests in the form of unit tests.

If you enforce a certain amount of code coverage, be sure to measure that coverage on unit tests only.