![]() |
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 StepsAt 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.
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 PrincipleTo evolve a whole universe, at least these things need to happen:
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 ThemNow 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.
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 ClassesSo 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 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 If the universe is to be immutable, then its state should be provided in its constructor. This constructor is called by 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 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 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 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 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 ConclusionI 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? Filed under: Software Development Tagged: bottom-up, code retreat, factory method, Game of Life, immutable, individual contributor, interaction-based testing, manager, mock objects, Mockito, Single Responsibility Principle, state-based testing, TDD, top-down ![]() |
