Bootcamp
Search…
9.1.2: TDD
TDD stands for Test Driven Development. TDD is a process of writing code that begins by explicitly translating the behaviours and requirements of an application into running tests. Before a line of the application code is written, a test will be written that defines that behaviour. Then the code to pass that test is added into the application.

Dice Game Rules

Directions for play:

Roll the dice and keep the highest.
Roll the remaining dice and again set aside the highest.
Roll the last die, and add up your total. Write down your score.
If a player roils the highest total for a round, they win the round.
Keep score and the player who wins the most turns wins the game.

TDD

In writing the code for this game a few things are assumed:
  • the architecture for this game is similar to the card game
  • the game has DOM manipulation code that we will not be testing
  • the game logic is focused around a set of functions that take the current state of the game as parameters and return the new state

Starter Code

test/dice.js

1
import { assert } from 'chai';
2
​
3
import { playGame } from '../dice.js';
4
​
5
describe('Dice Game', function () {});
Copied!

dice.js

1
const rollDice = () => {
2
const randomDecimal = Math.random() * 6;
3
return Math.floor(randomDecimal) + 1;
4
};
5
​
6
export function playGame(gameState) {}
Copied!

Workflow

TDD workflow means that you begin by writing a failing test.
Now that we understand how the game works we can write our first test. This test will define the first behaviour of our game. We can start chronologically- with the first action of the game.
The first piece of data we would want to verify is that it's the correct user's turn.
1
it('Game starts with player one.', function () {
2
const result = playGame();
3
expect(result.player).to.equal(1);
4
});
Copied!
Now that we've created a test we can begin to make the test not fail. In TDD this doesn't meant that we implement the entire game, or even the entire feature, but that we fix the failing test.
1
export function playGame(gameState) {
2
return {
3
player: 1,
4
};
5
}
Copied!
Within the rules of the game, i.e., the requirements of the application, the next thing that happens is the gameplay. This suggests that we should leave off any further development on the user turn and implement the next part of the game (rather than the next part of the player turn feature).
1
it('Game rolls three dice.', function () {
2
const game = playGame();
3
expect(game.dice.length).to.equal(3);
4
});
Copied!
Now we can write the code that passes this test.
1
export function playGame(gameState) {
2
const dice = [rollDice(), rollDice(), rollDice()];
3
return {
4
dice,
5
player: 1,
6
};
7
}
Copied!
The rules say that the highest dice is selected. Because we want to avoid testing randomness, we can just check that the value exists. We'll use a value that will also keep the running tally as the game progresses.
1
it('Keeps the highest of the three dice in a running tally.', function () {
2
const game = playGame();
3
expect(game.turnScore).exists;
4
// this is for one roll
5
expect(game.turnScore).to.be.below(7);
6
});
Copied!
Now we can make the test pass.
1
export function playGame(gameState) {
2
const dice = [rollDice(), rollDice(), rollDice()];
3
const maxDiceRoll = Math.max(...dice);
4
return {
5
dice,
6
player: 1,
7
turnScore: maxDiceRoll,
8
};
9
}
Copied!
The next thing the rules say is: Roll the remaining dice and again set aside the highest.
Now we know we need to make some structural changes to my code in order to implement this rule.
We'll adjust the output of the game function for this part of the game. Now I also have to edit the other test that relies on the dice array value.
1
it('Keeps the highest of the two dice in a running tally.', function () {
2
var gameState1 = playGame();
3
expect(game.numberOfDice).to.equal(3);
4
​
5
var gameState2 = playGame(gameState1);
6
expect(gameState2.numberOfDice).to.equal(2);
7
​
8
expect(gameState2.turnScore).exists;
9
});
Copied!
Now I can write the code that passes this test.
1
export function playGame(gameState) {
2
let numberOfDice;
3
​
4
if (!gameState) {
5
numberOfDice = 3;
6
} else {
7
numberOfDice = gameState.numberOfDice - 1;
8
}
9
​
10
const dice = [];
11
for (let i = 0; i < numberOfDice; i += 1) {
12
dice.push(rollDice());
13
}
14
​
15
const maxDiceRoll = Math.max(...dice);
16
​
17
return {
18
dice,
19
player: 1,
20
numberOfDice,
21
turnScore: maxDiceRoll,
22
};
23
}
Copied!
We can see that for this test a major structural change to the code was necessary. TDD does not get rid of the requirement to rewrite large portions of the code or to change around the logic of how the code works. TDD is simply the guiding principle for how to progress through a set of code requirements. We can more confidently refactor the code because we have in place tests that verify all the behaviour we've coded so far.
Now we can add the last test for this turn. After verifying that the test runs we can duplicate it and add another test to the bottom, that tests the player turn functionality.
1
it('Keeps the highest of the last dice in a running tally.', function () {
2
var gameState1 = playGame();
3
​
4
var gameState2 = playGame(gameState2);
5
​
6
var gameState3 = playGame(gameState2);
7
expect(gameState3.numberOfDice).to.equal(1);
8
​
9
expect(gameState3.turnScore).exists;
10
});
11
​
12
it('Changes turn after the first player rolls 3 times.', function () {
13
var gameState1 = playGame();
14
expect(gameState1.numberOfDice).to.equal(3);
15
expect(gameState1.player).to.equal(1);
16
​
17
var gameState2 = playGame(gameState1);
18
​
19
var gameState3 = playGame(gameState2);
20
expect(gameState3.numberOfDice).to.equal(1);
21
​
22
expect(gameState3.player).to.equal(2);
23
});
Copied!
When this test fails, we can write the code that will pass this test:
1
export function playGame(gameState) {
2
let numberOfDice;
3
​
4
if (!gameState) {
5
numberOfDice = 3;
6
} else {
7
numberOfDice = gameState.numberOfDice - 1;
8
}
9
​
10
const dice = [];
11
for (let i = 0; i < numberOfDice; i += 1) {
12
dice.push(rollDice());
13
}
14
​
15
const maxDiceRoll = Math.max(...dice);
16
​
17
let player;
18
if (!gameState) {
19
player = 1;
20
} else if (numberOfDice === 1) {
21
player = gameState.player + 1;
22
} else {
23
player = gameState.player;
24
}
25
​
26
return {
27
dice,
28
player,
29
numberOfDice,
30
turnScore: maxDiceRoll,
31
};
32
}
Copied!

Test Code so Far

1
import { expect } from 'chai';
2
​
3
import { playGame } from '../dice.js';
4
​
5
describe('Dice Game', function () {
6
it('Game starts with player one.', function () {
7
const game = playGame();
8
expect(game.player).to.equal(1);
9
});
10
​
11
it('Game rolls three dice.', function () {
12
const game = playGame();
13
expect(game.numberOfDice).to.equal(3);
14
});
15
​
16
it('Keeps the highest of the three dice in a running tally.', function () {
17
const game = playGame();
18
expect(game.turnScore).exists;
19
// this is for one roll
20
expect(game.turnScore).to.be.below(7);
21
});
22
​
23
it('Keeps the highest of the two dice in a running tally.', function () {
24
var game = playGame();
25
expect(game.numberOfDice).to.equal(3);
26
​
27
var game = playGame(game);
28
expect(game.numberOfDice).to.equal(2);
29
​
30
expect(game.turnScore).exists;
31
});
32
​
33
it('Keeps the highest of the last dice in a running tally.', function () {
34
var game = playGame();
35
expect(game.numberOfDice).to.equal(3);
36
​
37
var game = playGame(game);
38
expect(game.numberOfDice).to.equal(2);
39
​
40
var game = playGame(game);
41
expect(game.numberOfDice).to.equal(1);
42
​
43
expect(game.turnScore).exists;
44
});
45
​
46
it('Changes turn after the first player rolls 3 times.', function () {
47
var game = playGame();
48
expect(game.player).to.equal(1);
49
​
50
var game = playGame(game);
51
​
52
var game = playGame(game);
53
​
54
expect(game.player).to.equal(2);
55
});
56
});
Copied!

Exercise

Copy this code into a copy of the starter code repo. Implement more of the game features in a TDD style.

Further Reading

Past students have found the following resources helpful.
  1. 1.
    TDD Intro with JavaScript: https://www.youtube.com/watch?v=Jv2uxzhPFl4​