Project: Battleship (v1)

Introduction

It’s time to really flex your muscles. Test Driven Development can certainly feel uncomfortable at first, but becomes more natural with practice. We’re going to implement the classic game ‘Battleship’. If you’ve never played it, or need a refresher you can read about it here and you can play an online version here.

Since we’re doing TDD, it’s important that you don’t get overwhelmed. Simply take it one step at a time. Write a test, then make it pass.

We have not yet discussed testing the appearance of a webpage. Doing this requires a separate set of tools, and it is outside the scope of this unit. For this assignment do your best to isolate every bit of application functionality from the actual DOM manipulation bits. You can use mocks to make sure that DOM methods like appendChild are being called, but try your best to keep those things outside of the app logic.

Assignment

  1. Begin your app by creating the Ship factory function.
    1. Your ‘ships’ will be objects that include their length, where they’ve been hit and whether or not they’ve been sunk.
    2. REMEMBER you only have to test your object’s public interface. Only methods or properties that are used outside of your ‘ship’ object need unit tests.
    3. Ships should have a hit() function that takes a number and then marks that position as ‘hit’.
    4. isSunk() should be a function that calculates it based on their length and whether all of their positions are ‘hit’.
  2. Create Gameboard factory.
    1. Note that we have not yet created any User Interface. We should know our code is coming together by running the tests. You shouldn’t be relying on console.logs or DOM methods to make sure your code is doing what you expect it to.
    2. Gameboards should be able to place ships at specific coordinates by calling the ship factory function
    3. Gameboards should have a receiveAttack function that takes a pair of coordinates, determines whether or not the attack hit a ship and then sends the ‘hit’ function to the correct ship, or records the coordinates of the missed shot.
    4. Gameboards should keep track of missed attacks so they can display them properly.
    5. Gameboards should be able to report whether or not all of their ships have been sunk.
  3. Create Player.
    1. players can take turns playing the game by attacking the enemy Gameboard.
    2. The game is played against the computer, so make ‘computer’ players capable of making random plays. The AI does not have to be smart, but it should know whether or not a given move is legal. (i.e. it shouldn’t shoot the same coordinate twice).
  4. Create the main game loop and a module for DOM interaction.
    1. At this point it is appropriate to begin crafting your User Interface.
    2. The game loop should set up a new game by creating Players and Gameboards. For now just populate each Gameboard with predetermined coordinates. You can implement a system for allowing players to place their ships later.
    3. We’ll leave the HTML implementation up to you for now, but you should display both the player’s boards and render them using information from the Gameboard class.
      1. You need methods to render the gameboards and to take user input for attacking. For attacks, let the user click on a coordinate in the enemy Gameboard.
    4. The game loop should step through the game turn by turn using only methods from other objects. If at any point you are tempted to write a new function inside the game loop, step back and figure out which class or module that function should belong to.
    5. Create conditions so that the game ends once one players ships have all been sunk. This function is appropriate for the Game module.
  5. Finish it up
    1. There are several options available for letting users place their ships. You can let them type coordinates for each ship, or investigate implementing drag and drop.
    2. You can polish the Intelligence of the computer player by having it try adjacent slots after getting a ‘hit’.
    3. Optionally, create a 2 player option that lets users take turns by passing the device back and forth. If you’re going to go this route, make sure the game is playable on a mobile screen and implement a ‘pass device’ screen so that players don’t see each others boards!

When you finish, create a new branch in the forked exercises repository and submit the Pull Request in the Quiz below.

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

More Testing (v1)

Introduction

An important basic concept in testing is isolation. You should only test one method at a time, and your tests for one function should not depend upon an external function behaving correctly – especially if that function is being tested elsewhere. The main reason for this is that when your tests fail, you want to be able to narrow down the cause of this failure as quickly as possible. If you have a test that depends on several functions, it can be hard to tell exactly what is going wrong.

Pure Functions

There are many benefits to using Test Driven Development (TDD) when you write your code. One of the biggest benefits is less obvious at first – it helps you to write better code. If you look back at some of your early projects you will probably notice how tightly coupled everything is. All of your functions include references to functions in other parts of your code, and the whole thing is filled with DOM methods or console.log().

Tightly coupled code is hard to test! Imagine trying to write tests for a function like this:

function guessingGame() {
  const magicNumber = 22;
  const guess = prompt('guess a number between 1 and 100!');
  if (guess > magicNumber) {
    alert('YOUR GUESS IS TOO BIG');
  } else if (guess < magicNumber) {
    alert('YOUR GUESS IS TOO SMALL');
  } else if (guess == magicNumber) {
    alert('YOU DID IT! 🎉');
  }
}

Making this testable requires us to split up all the different things that are happening. First, we do not need to test the functions prompt and alert because they are built in to the browser. They are external to our program and whoever wrote them has already tested them. What we do need to test is the number logic, which is much easier if we untangle it from the other functions:

function evaluateGuess(magicNumber, guess) {
  if (guess > magicNumber) {
    return 'YOUR GUESS IS TOO BIG';
  } else if (guess < magicNumber) {
    return 'YOUR GUESS IS TOO SMALL';
  } else if (guess == magicNumber) {
    return 'YOU DID IT! 🎉';
  }
}

function guessingGame() {
  const magicNumber = 22;
  const guess = prompt('guess a number between 1 and 100!');
  const message = evaluateGuess(magicNumber, guess);
  alert(message);
}

guessingGame();

In this example, the only thing we really need to test is the evaluateGuess function, which is much easier to test because it has a clear input and output and doesn’t call any external functions. This implementation is much nicer as well because it’s much easier to extend. If we wanted to switch out the prompt and alerts for methods that manipulate the DOM we can do that more simply now and if we want to make our game more advanced by letting the user make multiple guesses, that is also easier.

If we had written this program with TDD it is very likely that it would have looked more like the second example to begin with. Test driven development encourages better program architecture because it encourages you to write Pure Functions.

Mocking

There are two solutions to the ‘tightly coupled code’ problem. The first, and best option is to simply remove those dependencies from your code as we did above, but that is simply not always possible. The second option is mocking – writing "fake" versions of a function that always behaves exactly how you want. For example, if you’re testing a function that gets information from a DOM input, you really don’t want to have to set up a webpage and dynamically insert something into the input just to run your tests. With a mock function, you could just create a fake version of the input-grabbing function that always returns a specific value and use THAT in your test.

Study

  1. If you haven’t already, watch the mocking videos from this series:

  1. Too much mocking can be a bad thing. It is sometimes necessary, but if you have to set up an elaborate system of mocks to test any bit of your code, that means your code is too tightly coupled. These two articles (one and two) might be a little extreme, but they contain several really good points about program architecture and testing.

  2. Now that you have some practice and context for TDD, this section of the Jest docs will probably make good sense to you.

  3. Jest includes some really handy mocking functions. Read about them in the official docs

  4. And finally, if you wish, you can add Jest to your webpack setup. Read about that process here.

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

UPDATED: 01.02.2021

Project: Testing Practice (v1)

Introduction

Let’s practice! This testing thing really is not that difficult, but it is quite new. The only way to get comfortable with it is to spend some time doing it.

Practice

Write tests for the following functions, and then make the tests pass!

  1. capitalize(string) takes a string and returns that string with the first character capitalized.

  2. reverseString(string) takes a string and returns it reversed.

  3. A calculator object that contains the basic operations: add, subtract, divide, and multiply.

  4. Caesar Cipher. Read about it on this website and play around with this cool Caesar Cipher Exploration Tool from Khan Academy.

    1. Don’t forget to test wrapping from z to a.
    2. Don’t forget to test keeping the same case.
    3. Don’t forget to test punctuation!
    4. For this one, you may want to split the final function into a few smaller functions. One concept of Testing is that you don’t need to explicitly test every function you write… Just the public ones. So in this case you only need tests for the final caesar() function. If it works as expected you can rest assured that your smaller helper functions are doing what they’re supposed to.
  5. Array Analysis. Write a function that takes an array of numbers and returns an object with the following properties: average, min, max, and length.

    const object = analyze([1,8,3,4,2,6]);
    
    object == {
      average: 4,
      min: 1,
      max: 8,
      length: 6
    };
    

Special Note on using ES6 import statements with Jest

By default, the current version of Jest will not recognize ES6 import statements. In order for you to be able to use ES6 modules for this project you may do the following:

  1. Install the @babel/preset-env package
npm i -D @babel/preset-env
  1. Create a .babelrc file in the project’s root with the following lines of code:
{
  "presets": ["@babel/preset-env"]
 }

This will allow you to use import statements. Note that in the Jest docs a similar instruction is laid out here

Additional Resources

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

UPDATED: 20.01.2021

Testing Basics (v1)

Introduction

Test Driven Development is a big deal in the modern development landscape. This is a concept that we introduced way back in our Fundamentals section with our JavaScript Exercises. The main idea is simply that you start working on your code by writing automated tests before writing the code that is being tested. There are tons of benefits to working like this, all of which will be discussed in the resources below.

There are many test-running systems available in JavaScript: Mocha, Jasmine, Tape and Jest to name a few. Fortunately the syntax for each one is very similar. They all have their own set of special features, but the basic syntax is almost identical, so in the end it doesn’t matter which one you use. In fact, simply picking which library to use for this curriculum has been quite tricky!

This lesson is going to center around Jest. The biggest reasons for this decision are that one of the best resources we’ve found for explaining JavaScript testing uses it and they have fantastic documentation. In the end, writing tests is less about the syntax and more about the TDD philosophy. The most important issues are knowing why we write tests and what we test rather than how.

Study

  1. Read this short article that outlines the basic process and the benefits of TDD.

  2. Watch at least the first 3 videos of this video series about testing in JavaScript. The first video focuses heavily on the WHY, while the next two go into more depth about the process. Later videos in the series are definitely worthwhile, but the first 3 are enough to get you up and running.

  3. Create a new folder, be careful the name you give it should not contain any spaces (e.g no: "Jest App", yes: "JestApp"). Open Visual Studio Code and navigate to that folder. Open the terminal and type npm init --yes to initialize a default package.json file. Then read and follow the Getting Started tutorial on the main Jest website.

  4. Read and follow the Using Matchers document on the main Jest website. This one demonstrates some of the other useful functions you can use in your tests.

  5. Watch this amazing next video that covers what to test in your codebase. The video is specifically about testing the Ruby language, but that doesn’t matter at all. The concepts here ring true in any language, and luckily Ruby is a clear enough language that you will be able to follow along just fine. This video might be worth re-visiting after you’ve done some testing of your own.

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

UPDATED: 08.03.2021

Project: Battleship

Introduction

In this project you are going to implement the classic game ‘Battleship’. If you’ve never played it, or need a refresher you can read about it here. If you like you can play an online version here.

Test Driven Development can certainly feel uncomfortable at first, but becomes more natural with practice. Since you are going to do TDD it is important not to get overwhelmed. Just simply write a test and make it pass.

Up until now we have not discusses about testing the appearance of a webpage. The reason is that this testing requires a separate set of tools, and it is outside the scope of this lesson.

As for this project do your best to isolate every bit of application functionality from the actual DOM manipulation bits. If you want you can use mocks to make sure that DOM methods like appendChild are being called. However try your best to keep those things outside of the app logic.

Assignment

  1. Begin your app by creating the Ship factory function.
    1. Your ‘ships’ will be objects that include their length, where they’ve been hit and whether or not they’ve been sunk.
    2. REMEMBER you only have to test your object’s public interface. Only methods or properties that are used outside of your ‘ship’ object need unit tests.
    3. Ships should have a hit() function that takes a number and then marks that position as ‘hit’.
    4. isSunk() should be a function that calculates it based on their length and whether all of their positions are ‘hit’.
  2. Create Gameboard factory.
    1. Note that we have not yet created any User Interface. We should know our code is coming together by running the tests. You shouldn’t be relying on console.logs or DOM methods to make sure your code is doing what you expect it to.
    2. Gameboards should be able to place ships at specific coordinates by calling the ship factory function
    3. Gameboards should have a receiveAttack function that takes a pair of coordinates, determines whether or not the attack hit a ship and then sends the ‘hit’ function to the correct ship, or records the coordinates of the missed shot.
    4. Gameboards should keep track of missed attacks so they can display them properly.
    5. Gameboards should be able to report whether or not all of their ships have been sunk.
  3. Create Player.
    1. players can take turns playing the game by attacking the enemy Gameboard.
    2. The game is played against the computer, so make ‘computer’ players capable of making random plays. The AI does not have to be smart, but it should know whether or not a given move is legal. (i.e. it shouldn’t shoot the same coordinate twice).
  4. Create the main game loop and a module for DOM interaction.
    1. At this point it is appropriate to begin crafting your User Interface.
    2. The game loop should set up a new game by creating Players and Gameboards. For now just populate each Gameboard with predetermined coordinates. You can implement a system for allowing players to place their ships later.
    3. We’ll leave the HTML implementation up to you for now, but you should display both the player’s boards and render them using information from the Gameboard class.
      1. You need methods to render the gameboards and to take user input for attacking. For attacks, let the user click on a coordinate in the enemy Gameboard.
    4. The game loop should step through the game turn by turn using only methods from other objects. If at any point you are tempted to write a new function inside the game loop, step back and figure out which class or module that function should belong to.
    5. Create conditions so that the game ends once one players ships have all been sunk. This function is appropriate for the Game module.
  5. Finish it up
    1. There are several options available for letting users place their ships. You can let them type coordinates for each ship, or investigate implementing drag and drop.
    2. You can polish the Intelligence of the computer player by having it try adjacent slots after getting a ‘hit’.
    3. Optionally, create a 2 player option that lets users take turns by passing the device back and forth. If you’re going to go this route, make sure the game is playable on a mobile screen and implement a ‘pass device’ screen so that players don’t see each others boards!

When you finish, create a new branch in the forked exercises repository and submit the Pull Request in the Quiz below.


The archived old version of this post can be found here

UPDATED: 09.06.2021

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

More Testing

Introduction

It is important to remember that a key factor to testing is isolation. In other words, you should only test one method at a time. In addition, the tests you do for one function, should not depend upon an external function behaving correctly, especially if that function is being tested elsewhere.

This is due to the fact that if something goes wrong you won’t be able to narrow down the cause of the failure as quickly as possible. Thus, if a test fails and that test depends on several functions, it will be tricky to find out what exactly goes wrong.

TDD & Pure Functions

As already stated in previous topics there are many benefits to using Test Driven Development (TDD) when we write our code. One of the most important and clear benefits is that it helps us write better code.

If you go back to your old projects you will notice a few things. Everything is tightly coupled and your functions include references to functions in other parts of your code. Furthermore, you will notice that the entire code is filled with DOM methods or console.log(). Tightly coupled code is hard to test!

Let’s see an example. Imagine you have the following function and you try to write tests for it.

function guessNumGame() {
  const randomNum = 18;
  const guessNum = prompt('Guess a number between 1 and 100!');
  if (guessNum > randomNum) {
    alert('The number you guessed is bigger');
  } else if (guessNum < randomNum) {
    alert('The number you guessed is smaller');
  } else if (guessNum == randomNum) {
    alert('Congrats! You guessed the correct number!');
  }
}

In order to test the above function we need to split up all the different things that are happening. To be more specific what we need to test the is the number logic of our function. Since the functions prompt and alert are built in to the browser, we don’t need to test them. The number logic would be much easier to be tested if we untangle it from the other functions like so:

function evaluateNum(randomNum, guessNum) {
  if (guessNum > randomNum) {
    return 'The number you guessed is bigger';
  } else if (guessNum < randomNum) {
    return 'The number you guessed is smaller';
  } else if (guessNum == randomNum) {
    return 'Congrats! You guessed the correct number!';
  }
}

function guessNumGame() {
  const randomNun = 18;
  const guessNum = prompt('Guess a number between 1 and 100!');
  const result = evaluateNum(randomNun, guessNum);
  alert(result);
}

guessNumGame();

So now the only thing we need to test is the evaluateNum() function. This function is now much easier to test since it has a clear input and output and doesn’t call any external functions. In addition this implementation is much nicer because we can extend it with no hassle. For example we could add additional functionality to our function such as letting the user make multiple guesses and that would be mush simpler now.

Test driven development encourages better program architecture because it encourages you to write Pure Functions.

What is Mocking?

As we said before ‘tightly coupled code’ is a big problem when it comes to testing. However there are two solutions to this problem. The first option, which is the best of the two, is the one we did before where we removed the dependencies from our code. Unfortunately that is not always possible.

The second option is mocking. Mocking is the process of writing "fake" versions of a function that always behaves exactly how we want. For example, if we are testing a function that gets information from a DOM input, we don’t want to have to set up a webpage and dynamically insert something into the input just to run our tests. However, with a mock function, we could just create a fake version of the input-grabbing function that always returns a specific value and we use THAT in our test.

Study

  1. Watch the mocking videos from this series, if you haven’t done it already.

  1. Too much mocking can be a bad thing. These two articles one and two although might be a little extreme, they contain several really good points about program architecture and testing.

  2. Read this section of the Jest docs which will probably make good sense to you, now that you have a better idea of TDD.

  3. Read the official Jest docs which include some really handy mocking functions.

  4. If you want you can add Jest to your webpack setup. Read about that process here.


The archived old version of this post can be found here

UPDATED: 08.06.2021

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

Project: Testing Practice

Introduction

Now it is time for practice!! Although testing isn’t something difficult it is quite new. Therefore spending more time to it you will get more comfortable.

Practice

Write tests for the following functions, and then make the tests pass!

  1. capitalize(string) takes a string and returns that string with the first character capitalized.

  2. reverseString(string) takes a string and returns it reversed.

  3. A calculator object that contains the basic operations: add, subtract, divide, and multiply.

  4. Caesar Cipher. Read about it on this website and play around with this cool Caesar Cipher Exploration Tool from Khan Academy.

    1. Don’t forget to test wrapping from z to a.
    2. Don’t forget to test keeping the same case.
    3. Don’t forget to test punctuation!
    4. For this one, you may want to split the final function into a few smaller functions. One concept of Testing is that you don’t need to explicitly test every function you write… Just the public ones. So in this case you only need tests for the final caesar() function. If it works as expected you can rest assured that your smaller helper functions are doing what they’re supposed to.
  5. Array Analysis. Write a function that takes an array of numbers and returns an object with the following properties: average, min, max, and length.

    const object = analyze([1,8,3,4,2,6]);
    
    object == {
      average: 4,
      min: 1,
      max: 8,
      length: 6
    };
    

Special Note on using ES6 import statements with Jest

By default, the current version of Jest will not recognize ES6 import statements. Therefore, in order for you to be able to use ES6 modules for this project you need to do the following:

  1. Install the @babel/preset-env package
npm i -D @babel/preset-env
  1. Create a .babelrc file in the project’s root with the following lines of code:
{
  "presets": ["@babel/preset-env"]
 }

The code above will allow you to use import statements. In the Jest docs a similar instruction is laid out here

Additional Resources

In this section you can find a lot of helpful links to other content. This is a supplemental material for you if you want to dive deeper into some concepts.


The archived old version of this post can be found here

UPDATED: 08.06.2021

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

Testing Basics

Introduction

As we have seen in a previous topic TDD (Test Driven Development) is a real important concept to web development. Basically TDD refers to the practice of writing automated tests before completing the entire code. In other words, you write some code and then you test it before you write more code.

There are tons of benefits to working like this, all of which will be discussed in the resources below. There are many test-running systems available in JavaScript: Mocha, Jasmine, Tape and Jest are some of them.

All these systems have a similar syntax. Although each one has its own set of special features, the basic syntax is almost identical. Therefore it doesn’t matter which one you use.

In this topic we are going to center around Jest. The biggest reasons for this decision are that one of the best resources we’ve found for explaining JavaScript testing uses it and they have fantastic documentation.

Writing tests is about the TDD philosophy and not about the syntax. The most important issues are knowing why we write tests and what we test rather than how.

Study

  1. Read this short article that outlines the basic process and the benefits of TDD.

  2. This video series is about testing in JavaScript. Watch at least the first 3 videos. The first video focuses heavily on the WHY, while the next two go into more depth about the process.

  3. Create a new folder, be careful the name you give it should not contain any spaces (e.g no: "Jest App", yes: "JestApp"). Open Visual Studio Code and navigate to that folder. Open the terminal and type npm init --yes to initialize a default package.json file. Then read and follow the Getting Started tutorial on the main Jest website.

  4. Read and follow the Using Matchers document on the main Jest website. This one demonstrates some of the other useful functions you can use in your tests.

  5. Watch the next video which is amazing and covers what to test in your codebase. The video is specifically about testing the Ruby language, but that doesn’t matter. The concepts here ring true in any language, and luckily Ruby is a clear enough language that you will be able to follow along just fine. Keep in mind that this video might be worth re-visiting after you’ve done some testing of your own.


The archived old version of this post can be found here

UPDATED: 09.06.2021

Feedback

  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.