Skip to content

oranmornz/tdd-bowling-logic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JavaScript Bowling Kata

This kata aims to help you become familiar with test-first design, also commonly called test-driven development, or TDD. TDD often results in stronger software design and significantly reduced complexity, as well as increased test coverage! For these reasons, many teams prefer writing tests first.

Setup

More about installation
  1. Clone this repository
  2. npm install

0. Preparing a test runner

  • npm install -D vitest
  • Change the test script in package.json to "vitest"
More about the test script

You'll see a line in package.json that says

"test": "echo \"Error: no test specified\" && exit 1"

This is what will happen when you run npm test or npm t. Instead of echoing an error, replace it with "vitest" so it reads

"test": "vitest"

Tests (which drive development)

1. Create a sample test

  • Create a test that will always pass

    More about the sample test

    This help us check that our setup is working. Create score.test.js with these contents.

      import { test, expect } from 'vitest'
    
      test('test setup working', () => {
        expect(true).toBeTruthy()
      })
  • Run the tests

    More about running tests

    Run tests on the command line with npm test and celebrate if we're getting a passing test. Next, we will commit the working test.

  • Commit our work

    More about committing
    git add -A
    git commit -m "Test setup working"

2. Preparing to test frames

  • import a new file, ./score, from score.test.js

    More about importing

    At the top of score.test.js, add a reference to the code we intend to test.

    import * as score from './score' // this is the line to add

    Now you'll notice we are getting an error because it can't find the score reference (because it doesn't exist yet).

  • Create score.js which exports an empty object

    More about score.js

    Create a new score.js file in the top level of your project. For now, it will contain the minimum amount of code required to get the test to pass.

    export {}

    After saving these changes, the single test should once again run and pass.


Scoring frames

3. Gutterball frames

Now, we will test our least complicated frame.

  • Create a test for returning the score of a gutterball frame. It will fail

    More about the gutterball test
    test('scores a gutterball frame', () => {
      const frame = [0, 0]
      const expected = 0
      const actual = score.scoreFrame(frame)
    
      expect(actual).toBe(expected)
    })

    Now our test is failing because it can't find the scoreFrame method. So let's add it.

  • Add the scoreFrame method to ./score.js and export it

    More about the scoreFrame method

    To ./score.js, modify the exports and add the scoreFrame method

    export {
      scoreFrame
    }
    
    function scoreFrame (frame) {
    }

    Now our test is failing because it returned the wrong value (undefined) instead of what it was expecting (0). So let's return what it wants.

  • Cause scoreFrame to return the value expected by our test

    More about the scoreFrame return value

    The quickest way to get our tests passing again is for scoreFrame to return 0.

    function scoreFrame (frame) {
      return 0
    }

    Sweet! Our tests are passing again. Let's commit it.

  • Commit our work

    More about committing
    git add -A
    git commit -m "Scoring gutterball frames"

4. Normal frames

Now let's add a feature that can score a normal frame (one without a spare or a strike).

  • Write a test to score a normal frame

    More about the normal frame test

    Our test might look like this:

    test('scores a normal frame', () => {
      const frame = [2, 3]
      const expected = 5
      const actual = score.scoreFrame(frame)
      expect(actual).toBe(expected)
    })

    This new test is failing because we were expecting a 5 and 0 was returned. Apparently our scoreFrame method needs to do something more than return 0.

  • Update the scoreFrame function to pass the test

    More about updating scoreFrame

    Instead of returning zero, the scoreFrame function should accept a frame and use its two values to return the score. Complete the scoreFrame function to pass the test.

    function scoreFrame (frame) {
      //?
    }

    But remember, the cycle is RED -> GREEN -> REFACTOR. Is there anything about our code that we could improve to make it more readable or DRY?

  • Commit our work

    More about committing
    git add -A
    git commit -m "Scoring normal frames"

5. Spare frames

From this point forward you will be writing the tests yourself!

  • Add a test for scoring a spare, and update scoreFrame to pass

    More about scoring spares

    To do this, we're going to need the next frame as well. You'll need to pass two arguments when calling the scoreFrame function.

    test('scores a spare frame', () => {
    })
  • Complete any refactoring, re-run the tests, and make a commit

6. Single strike frames

  • Add a test for scoring a single strike, and update scoreFrame to pass

    More about scoring single strikes Because a strike uses the next 2 rolls, if the first is another strike (called a double), we'll need yet another frame. Let's tackle the double scenario later. For now, let's handle the single-strike scenario.
    test('scores a single strike frame', () => {
    })
  • Complete any refactoring, re-run the tests, and make a commit (again)

7. Double strike frames

  • Add a test for scoring a double strike, and update scoreFrame to pass

    More about scoring double strikes

    Now let's implement that other strike scenario where we have 2 strikes in a row and need a third frame. First, a new test.

    test('scores a double strike frame', () => {
    })
  • Complete any refactoring, re-run the tests, and make a commit (yet again)

    Tips

    Once again, look for opportunities to refactor. Do you have a scoreStrikes function? Maybe isStrike and isSpare functions would be useful too. Run the tests and make sure they still pass. Cool, then commit our changes.


Scoring games

8. Score an entire game

  • Add a feature to score a whole game of ten frames

    More about scoring an entire game Now that we can score many types of frames, let's add a feature to score a whole game of 10 frames. Because the 10th frame has special behaviour if there is a strike or a spare in it, we'll leave that scenario out of this test and test it separately later. But we can still add normal, spare, single strike and double strike frames.
    test('scores a game', () => {
    })
  • Complete any refactoring, re-run the tests, and make a commit

9. Score a game with a strike or spare in the 10th frame

  • Add a feature to score a game with a strike or spare in the last frame

    More about scoring special 10th frames Now let's add a feature that calculates the 10th frame when it contains a strike or a spare. You guessed it, a test first.
    test('scores a spare in the 10th frame', () => {
    })

    Maybe we could create an isTenth variable and pass it when calling scoreFrame?

10. Score a perfect game

  • Add a test that scores a perfect game
More about a perfect game

A perfect game should result in a score of 300.

const frames = [
  [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 0], [10, 10, 10]
]

You may find that scoring a perfect game reveals some flaws in your logic (maybe when scoring the 9th frame?). Make adjustments until the tests pass!


Stretch

Create a client to consume the game module

Name it index.js so we can run it with npm start.

Perhaps have a look at the prompt package.


Provide feedback on this repo

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published