Roulette Kata – exploring the affect of Time and Randomness in TDD

I did this Kata in response to a challenge set by @jjefries1 here
attempted by @RonJeffries here

For ease of reference the summary of the kata is repeated here;

A roulette wheel has numbers from 0 to 36. A spin of the wheel takes 20 seconds and leaves the ball on  a random number.

Design and implement a roulette wheel using TDD. Think about how using time and random numbers affects your design. Consider how much functionality each test covers and what responsibilities you are testing.

I wanted to drive this in a way where I did not need to mock out clocks or random number generators. To do this I thought that a pattern I have seen used extensively before would be suitable; where the generation of tick events was the responsibility of one class, whilst another had the job of responding to those events. Secondly I planned to  going with the model of “Object’s send messages to share data” so I knew straight away that I would be looking at a tell don’t ask approach.

The first notes I made on the kata were:

This is event based so avoid “World” stuff in test/codelets and to try for public methods are have void return types.

That about constituted the amount of “up front design I did before hitting the keyboard.

My very first failing, as opposed to not compiling,  test looked like this;

@Test
public void shouldNotifyStoppedAfterSpin() {
WheelObserver wheelObserver = mock(WheelObserver.class);
RouletteWheel wheel = new RouletteWheel(wheelObserver);
wheel.spin();
verify(wheelObserver, times(1)).stopped();
}

Note that at this point I am not taking any interest in time at all. Only that the observer is notified that the wheel has stopped after the spin.

I questioned where to go next and in writing my summary from the kata on cyber-dojo I think I went the harder route to start with. I took on verifying random number outputs, this took several goes round the red/green/refactor cycle and the final test I ended up with was a bit of a behemoth;

@Test
public void shouldSpecifyRandomBallLocationWhenStopped() {
final boolean seenAll[] = new boolean[1];
seenAll[0] = false;
WheelObserver wheelObserver = new WheelObserver() {
Set<Integer> seen = new HashSet<Integer>();
public void stopped(final int location) {
if (location < 0 || location > 36)
throw new IllegalArgumentException();
seen.add(location);
if (seen.size() == 37) seenAll[0] = true;
}
};
RouletteWheel wheel = new RouletteWheel(wheelObserver);
for (int x = 0; x < 1000; x++)
{
wheel.spin(0);
wheel.tick(20000);
}
assertTrue(seenAll[0]);
}
view raw test.java hosted with ❤ by GitHub

To push the design forward my next test looked like this;

@Test
public void shouldSpecifyBallLocationOnceWhenStopped() {
WheelObserver wheelObserver = mock(WheelObserver.class);
RouletteWheel wheel = new RouletteWheel(wheelObserver);
wheel.spin(0);
wheel.tick(20000);
wheel.tick(20001);
verify(wheelObserver, times(1)).stopped(anyInt());
}
view raw gistfile1.txt hosted with ❤ by GitHub

This as you can see led to some changes in the interface. Which I didn’t jump straight to but my RouletteWheel class was starting to take shape;

import java.util.Random;
public class RouletteWheel {
private final WheelObserver wheelObserver;
private long timeStartMs;
private final Random random = new Random();
public RouletteWheel(final WheelObserver wheelObserver) {
this.wheelObserver = wheelObserver;
}
public void spin(final long timeStartMs) {
this.timeStartMs = timeStartMs;
}
public void tick(final long timeMs) {
if ((timeMs - timeStartMs) >= 20000)
{
final int location = random.nextInt(37);
this.wheelObserver.stopped(location);
}
}
}
view raw RouletteWheel.java hosted with ❤ by GitHub

There were quite a few more evolutions of this but the next interesting bit of work to talk about on a TickProvider class, something to generate those tick events

@Test
public void shouldNotifyTickWhenStarted() {
RouletteWheel rouletteWheel = mock(RouletteWheel.class);
TickProvider tickProvider = new TickProvider();
tickProvider.start();
verify(rouletteWheel, atLeastOnce()).tick(anyInt());
}

Again we are getting the basic communication right without worrying about timing.

to get to that we eventually ended up with;

import org.junit.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.mockito.AdditionalMatchers.*;
import org.mockito.InOrder;
public class TickProviderTest {
@Test
public void shouldNotifyTickWhenStarted() {
RouletteWheel rouletteWheel = mock(RouletteWheel.class);
TickProvider tickProvider = new TickProvider(rouletteWheel);
tickProvider.start();
verify(rouletteWheel, timeout(150).atLeastOnce()).tick(anyInt());
}
@Test
public void shouldNotifyTickEvery100ms() throws Exception {
RouletteWheel rouletteWheel = mock(RouletteWheel.class);
TickProvider tickProvider = new TickProvider(rouletteWheel);
tickProvider.start();
verify(rouletteWheel, timeout(500).atLeast(5)).tick(anyInt());
}
@Test
public void shouldIncrementEachTickByApprox100() throws Exception {
RouletteWheel rouletteWheel = mock(RouletteWheel.class);
TickProvider tickProvider = new TickProvider(rouletteWheel);
InOrder inOrder = inOrder(rouletteWheel,
rouletteWheel,
rouletteWheel);
tickProvider.start();
Thread.sleep(200);
inOrder.verify(rouletteWheel)
.tick(and(gt((long)0), lt((long)20)));
inOrder.verify(rouletteWheel)
.tick(and(gt((long)90), lt((long)120)));
inOrder.verify(rouletteWheel)
.tick(and(gt((long)190), lt((long)220)));
}
}

Finally we got to the point where we needed to wire up an assembly of the RouletteWheel and the TickProvider. I did this by testing in TTDAIYMI (TDD as if you meant it) style writing the code directly in the test before extracting to a static method on RouletteWheel

@Test
public void shouldBuildGame() {
WheelObserver wheelObserver = mock(WheelObserver.class);
RouletteWheel rouletteWheel = new RouletteWheel(wheelObserver);
TickProvider tickProvider = new TickProvider(rouletteWheel);
rouletteWheel.spin(1000);
verify(wheelObserver, timeout(3000).times(1)).stopped(anyInt());
}
view raw shouldBuildGame.java hosted with ❤ by GitHub

The TDD approach has led my final code I think to show good separation of concerns between the generator of tick events (TickProvider), the consumer of those events (RouletteWheel) and the consumer of the stopped event that gives the final location of the ball (WheelObserver). This last one I particularly like as it totally separates presentation from the model, its also easy to envisage how the code could be adapted to publish intermediate events whilst the wheel was “spinning”.

import java.util.Random;
public class RouletteWheel {
private boolean spinning = false;
private final WheelObserver wheelObserver;
private long spinForMs;
private long currentMs = 0;
private final Random random = new Random();
public static void spin(final WheelObserver wheelObserver, final int spinDuration) {
RouletteWheel rouletteWheel = new RouletteWheel(wheelObserver);
TickProvider tickProvider = new TickProvider(rouletteWheel);
tickProvider.start();
rouletteWheel.spin(spinDuration);
}
RouletteWheel(final WheelObserver wheelObserver) {
this.wheelObserver = wheelObserver;
}
void spin(final long spinForMs) {
this.spinning = true;
this.spinForMs = spinForMs;
}
void tick(final long timeMs) {
currentMs = timeMs;
if (spinning && currentMs >= spinForMs)
{
spinning = false;
final int location = random.nextInt(37);
this.wheelObserver.stopped(location);
}
}
}
view raw RouletteWheel.java hosted with ❤ by GitHub
import java.util.Timer;
import java.util.TimerTask;
class TickProvider {
private final TimerTask tickTask;
TickProvider(final RouletteWheel rouletteWheel)
{
this.tickTask = new TimerTask() {
private long startMs = System.currentTimeMillis();
public void run() {
rouletteWheel.tick(System.currentTimeMillis() - startMs);
}
};
}
void start() {
Timer timer = new Timer();
timer.schedule(tickTask,0, 100);
}
}
view raw TickProvider.java hosted with ❤ by GitHub
public interface WheelObserver {
public void stopped(final int ballLocation);
}
view raw WheelObserver.java hosted with ❤ by GitHub

No doubt someone will point out some horrendous threading (or other) bug, please note this is a Kata or practice and I didn’t pair on it. But if you do spot one can I suggest you respond by proposing a test that would expose it? Remember TDD is not about passing when software works, its about improving your design and as a bonus you get some tests that should fail when it doesn’t behave as you intended. I hope I showed that here.

For the full gist including test classes see below;

gist https://gist.github.com/daveneedstoknow/4cbb6a7d707d8efe4cb5fff1ee748923

You can also see my working on the excellent cyber-dojo here I hope no-one will make further changes at the time of writing my work goes up to revison 106. Note that revisions after that may not be mine.

You’ll see (as is my typical experience of TDD and coding in general) that progress is not quite as linear as some explanations will have led you to believe, including here 😉

The total time I spent on this was around the 3 hour mark so higher end of allotted time (I took a break in the middle to teach my nephew some TDD 🙂

I enjoyed the Kata, thanks to @jjefries1 for suggesting it. I tackles some of the more challenging things in TDD but I think it also shows that if TDD is followed it does give you that nudge to a better final design.

Tagged ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: