Heads up! To view this whole video, sign in with your Courses account or enroll in your free 7-day trial. Sign In Enroll
Preview
Start a free Courses trial
to watch this video
It’s finally time to write the first tests for our Battleship game engine! In this video, we’ll start writing our BDD outline for Battleship. We’ll talk about describing the test suite, writing a test spec, and deciding test expectations.
Resources
Video review
- Adding “_test” to your test file names makes finding them more easily
- Mocha’s representation of a test suite – the
describe
function – takes two arguments: a string describing all the tests inside, and a function to wrap them all together - A suite will break at first (that's OK), because the function you're referring to may not exist yet
- The hardest part about BDD is deciding how the function we haven’t written yet might actually work.
- Even without writing tests, programmers still do a lot of guesswork as they program --- it's rare that you know exactly how every part of a program works at first
Let's start building our
Battleship game engine.
0:01
In Battleship, there are two grids,
one for each player.
0:03
Each player secretly places their
set of five ships on the grid and
0:07
then players take turns guessing
where the ships are located.
0:10
If you correctly guess that a ship is
located at a position on your opponent's
0:14
grid, your opponent marks
that spot as a hit.
0:19
If a ship has been hit at every unit
of its length the ship is sunk.
0:22
The first player to successfully sink all
of their opponent ships is the winner.
0:27
[APPLAUSE]
>> So
0:30
we'll need to write code that handles
the current player and winner,
0:33
the number of ships, their position,
and their status as active or sunk.
0:37
I know that sounds like a lot, but
0:42
just like our title case function
from the end of the previous stage,
0:43
we'll use BDD to break all this down
into small chunks by writing unit tests.
0:46
So let's start with the smallest
part of the game, the ships.
0:52
Inside the test directory, I'll create a
new test file to contain all of the tests
0:55
about ship features,
I'll call the file ship_test.js.
1:01
I like adding the _test
into my test file names so
1:06
that I can find them more easily later,
and it's a bit more obvious
1:10
what my error reports are trying
to tell me in the test output.
1:14
Okay, so
let's import expect from Chai first.
1:19
So at this point, I can already guess
at a few functions we might need.
1:29
We'll probably need some kind of function
to tell us whether a ship is located
1:33
at certain coordinates.
1:36
So for example, when a player puts a ship
on the board, or when a player tries to
1:38
attack, we need to check whether a ship is
positioned at the location they're naming.
1:42
So let's set up a new suite for
this function using describe, and
1:48
since the function is just going to check
for a ship, I'll name it checkForShip.
1:53
Remember, Mocha's
representation of a test suite,
2:08
the describe function,
takes two arguments.
2:11
A string describing all the tests inside,
and a function to wrap them all together.
2:14
So now,
let's import the checkForShip function
2:21
here at the top of our describe function.
2:24
Because all the tests in this
suite will need access to it.
2:26
Remember this suite will
just break at first,
2:42
because the function we're referring
to here doesn't exist at all.
2:45
This is just an outline for now.
2:49
So I'm making an educated guess that
we'll want to special directory named
2:51
game logic, to contain all of
our game logic functions, and
2:55
that will keep all the ship centered
functions in a file of their own.
2:59
So what should check for ship actually do?
3:03
Well, we'll probably give it a coordinate,
3:06
if there's no ship there I think it
should probably just return false.
3:08
We might change that later if our
functions work differently than we expect.
3:12
Maybe in the end we need it to return
more information than just false.
3:16
But for now, that makes sense,
so we'll start there.
3:19
In Mocha spec looks very
similar to a suite.
3:22
It takes two arguments, a string
describing our desired behavior, and
3:27
a function that wraps all of the specs,
expectations, and logic up together.
3:32
Each spec should be responsible for just
one aspect of the function's behavior.
3:40
So in order to describe
that particular behavior
3:45
of checkForShip, I'll name this test spec,
3:50
should correctly report no
ship at a given coordinate.
3:55
That's really informative, it communicates
what the spec is designed to prove.
4:04
And also, some useful information
about the nature of the function.
4:09
The function takes
a coordinate of some kind.
4:12
But I just realize that this
isn't enough information.
4:15
Because there are two boards in
Battleship, yours and your opponents.
4:18
So check for
ship will also need to accept a player so
4:22
that it knows which board is checking for
the given coordinates.
4:26
So I'll change this to should
correctly report no ship at a given
4:29
player's coordinate.
4:34
It's best to keep a tight feedback loop
in BDD, since I've written a lot of
4:38
test code already,
let's run our test and see what happens.
4:43
So in the console, I'll run npm tests.
4:47
And I see that I get an error.
4:54
It says error cannot find module.
4:56
Game logic ship methods.
5:00
Well, remember the ship methods
file isn't even really it.
5:02
So let's go fix that.
5:05
So I'll create a new
folder named game_logic.
5:13
Then, inside this new folder,
I'll create the ship_methods.js file.
5:20
I'll go ahead and keep my ship test
file open to compare against, and
5:35
just copy over any code I'll need later.
5:40
So I'll start by simply
defining the checkForShip
5:43
function inside the ship_methods.js file.
5:49
Then I'll export it by typing
module.exports.checkForShip,
5:58
Equals checkforShip.
6:06
All right, so that should be enough
to get over the import error.
6:12
So now if I go over to the console and
npm test,
6:15
cool, it shows that we're
passing our new test.
6:20
But we know that's a trick,
6:25
because that test doesn't actually
contain any expectations yet.
6:27
But at least we know where files
are connected properly and
6:30
that we set up our suite and
spec correctly.
6:33
Those sanity checks are helpful.
6:35
So let's write an expectation for
this spec now in the ship test js file.
6:41
For this spec to be meaningful,
it has to call the function we're testing.
6:47
So it's time to make some guesses about
how this function actually works.
6:50
We need to guess what kind of
parameters check for ship will accept,
6:54
in order to use it in our spec.
6:58
Now we could represent our coordinates in
lots of ways, a string of two numbers,
7:00
an array, an object and so on.
7:04
So I like the idea of arrays
since they come with lots of
7:06
useful methods built in
that we may want to use.
7:09
Again, I'm just making
educated guesses right now.
7:12
When I start writing the game,
7:16
the implementation details might
challenge our starting assumptions.
7:17
So it might be easier to come back and
7:21
adapt our tests than it would be to make
our functions match our expectations.
7:23
So the unit test we're writing just
gives me a starting point that
7:28
makes sense to me.
7:32
Inside the spec, I'll write our
expectation, to call checkForShip.
7:34
With a player and
7:44
an arbitrary location where none of
that player's ships are located.
7:45
So let's say 9,9.
7:52
And since there are no ships there
check for ship should return false.
7:58
So let's add to.be.false.
8:02
Okay, so
now if we run npm test in the console,
8:06
our test shows that our
spec isn't quite ready.
8:13
We're trying to pass checkForShip
arguments that we haven't defined yet.
8:17
The hardest part about BDD is deciding
how the function we haven't written yet
8:22
might actually work.
8:26
We have to make an example of the kind of
argument our function might expect, so
8:27
that we can use it in our spec correctly.
8:32
This is a lot like how we made up
an imaginary array of people objects
8:35
to test our gather names of function,
back in stage one.
8:39
The difference is that we don't
have the real function in hand yet.
8:43
And we're not adding that
cruft to our real code files.
8:46
These guess work function calls and
data structures are just going to live
8:50
here in our test spec isolated
from all of our production code.
8:55
The important thing is to focus on
the general behavior of the function we're
8:59
testing and not get bogged down in
the implementation details yet.
9:03
For now, I'll just invent something that
will get the test up and running and
9:06
kind of make sense.
9:10
And if I need to revise it later,
then that's okay.
9:11
At this point, I'm guessing it makes sense
for players to be represented as objects,
9:16
since they keep track of
different kinds of information.
9:19
So right above our expectation,
I'll create a player object.
9:22
Each player will have a set of ships, so
9:30
I'll set an array of ships
as a property on the object.
9:33
Again,there are lots of
ways to represent sets, but
9:38
I'll start with an array for convenience.
9:42
Since this test only needs us to
check one ship, that's all I'll add.
9:45
We'll store each ship's
location as a property.
9:50
So I'll add a locations array to the ship
and set it up with just one coordinate.
9:53
Let's say 0,0.
10:00
At this point we're assuming a lot about
the structure of players, ships, and
10:04
how we keep track of their location.
10:08
It's a lot of guesswork up front.
10:10
But even without writing tests,
10:12
programmers still do a lot
of guesswork as a program.
10:15
It's rare that you know exactly how
every part of a program works when
10:18
you first sit down at your computer.
10:21
So, it might feel a little strange to
write all of these tests before you start
10:23
writing your program.
10:27
Just keep in mind that most people
also feel awkward when they
10:30
first start using outlines for
their essays and blog posts.
10:33
If you feel overwhelmed by the set up,
10:37
just remember that you can always
work in a tighter feedback loop.
10:39
For example, you could have started this
set up work by only writing an object
10:43
name player to make that reference
error go away in our test output.
10:48
Then you would start with
another small step and
10:52
another until you had something
that looked similar to this.
10:54
Okay, so now I have a player and
it has one ship with one location.
10:58
And if I check for a ship at 9,9
checkForShip would return false,
11:04
because the ship that player
has is located at 0,0.
11:09
They don't have any ships yet
located at 9,9.
11:13
Running the test in the console
gives us a new assertion error,
11:23
it says expected, undefined, to be false.
11:28
Great, we get undefined
because our real check for
11:32
ship function doesn't return anything yet.
11:35
So it's time to start
writing our function.
11:37
So back inside shipmethods.js,
the check for
11:47
ship function will accept
the parameters player and coordinates.
11:51
First, I'll declare two variables inside
the function, shipPresent and ship.
11:59
Now I know I'm gonna have some kind of
conditional block, because I only want to
12:09
return false if the player has no
ships at the given coordinates.
12:13
I know I'll need to loop through all
the ships in the ships array and
12:17
check each of their coordinates against
the one we're giving the function.
12:21
So I'll just put this condition inside
a loop over all the player ships.
12:25
Next I'll save the current ship within
the loop to make things easier to read.
12:43
Finally, I'll filter the current ship's
location array down by comparing each
12:54
value to the given coordinates.
12:59
So again, this will filter
the current ship's location array for
13:18
matches against the given coordinate,
both the x and y numbers should match.
13:23
So this is going to return
on an array containing
13:29
only the coordinates that are a match.
13:32
And if nothing is a match,
the array will be empty.
13:34
So it will be a coordinate
only if there was a match.
13:59
The first member of an empty
array is undefined,
14:02
which will fail conditional and
it’s perfect.
14:05
It means we only need to save the first
element of the filtered array and
14:08
use that as a conditional check.
14:12
So if the array is empty that means
there weren't any matches to our given
14:13
coordinates and no ship is present.
14:17
Right below, we'll check whether
a ship is present at a given
14:21
coordinate by saying,
if no ship is present, return false.
14:26
All right, so
now we go over to the console and
14:37
run npm test to see if it works.
14:41
Great we get all green checks for
our tests.
14:46
You need to sign up for Treehouse in order to download course files.
Sign up