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.
- Adding “_test” to your test file names makes finding them more easily
- Mocha’s representation of a test suite – the
describefunction – 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