Code kata time - Fizzbuzz+Jest+Live webpage update

It’s been a while (too long) since I did a daily morning code kata, so I and the process hasn’t been published here so this might be a good time to benefit from both.

This morning I plan to do the fizz buzz kata, but with a twist. I want to use Jest this time for testing as I heard it recommended on this podcast called Gearing up for Assert(js) - an interview with JavaScript testing experts. I will also be doing it with LiveReload which I found out about yesterday too.

Setting up the test environment

My documents>programming>javascript folder has folders both for kata and testing, so in kata I’ll make a folder called fizzbuzz-2017-12-28

As I want to open the command prompt, I shift-rightclick on the folder, to find that there is only a PowerShell option there instead. Time to configure windows. A Google of replace powershell with command gives me How to replace PowerShell with Command Prompt on File Explorer’s Context Menu in Windows 10 which gives me some registry edits to give me back the option of a command prompt, which didn’t work.

Choice time - do I:

  • shift+rightclick on the folder and use powershell, or
  • run the command prompt separately and navigate to the folder

If I use powershell, that is easy to get to but it’s a new and uncomfortable environment that I don’t currently want to deal with.
If I run the command prompt separately and navigate to the folder, it can be difficult to navigate to the right place.

Powershell is being forced upon me so it must be bad, until further investigation occurs about it.
Aha! I’ll copy the folder path from Explorer, and paste that into the command prompt.

  1. Click on Explorer path and ctrl+c to copy
  • Win+R cmd [Enter]
  • cd ctrl+v [Enter]

And I’m there.

Installing Jest for this kata is next, and the getting started info is helpful. I already have npm installed, so it’s just a matter of doing

npm install --save-dev jest

Of the warnings that occur:

  • fsevents is optional, but I also don’t have its supported platform (that being mac) so I’m okay without it
  • enoent says I don’t have package.json - I had better remove jest then init a project before adding jest again
  • weak is skipped as being optional

So first, remove jest, init a new project, then learn about weak before installing jest again.

npm remove jest
npm init

I accept most of the default answers, but for Description say: “Code kata for morning warmup”, and for the test command I use jest as I plan for that to be used.

Investigating weak I don’t see myself having any use for that at this stage, but on further reading it’s used by Jest to detect memory leaks, so I’d better install weak, then Jest.

npm install --save-dev weak

An error occurs when attempting to install weak, because Python cannot be found. After some investigation I find from nodejs/node-gyp that I need to install it on windows from an elevated cmd.

  • Start menu → cmd → right-click on Command Prompt → Run as administrator

And then, issue the following command to install Python with all the required tools and configurations.

npm install --global --production windows-build-tools

This is taking some time to compile. The actual installer says:

Launched installers, now waiting for them to finish.
This will likely take some time - please be patient!

So - it’s time for some relevant XKCD while waiting.


I thought this was going to be easy, but I’ve found that this simple code kata task is an easy framework which allows you to deal with more complex things around it.

1 Like

Setting up the test environment

Python has now been installed, so I can go back to installing weak

npm install --save-dev weak

Which is successful now, but I am warned about not having a repository. Let’s get that dealt with by crating a github repository called fizzbuzz-2018-07-28

I’m instructed to create a readme file, using the following commands:

echo "# fizzbuzz-2017-12-28" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/pmw57/fizzbuzz-2017-12-28.git
git push -u origin master

I do have git installed so this goes well, and I can initialize the node project once more, to update the repository field.

npm init

Does installing weak this time result in no warnings? None are present, so I’m feeling good about this.
A lesson for next time - create the repository before starting each node project.

I should be able to install Jest now with less warnings too:

npm install --save-dev jest

The only warning that’s left is that I don’t have a Mac OS. I will not install a Mac computer to deal with that warning, and all is now good.

Test-driven development

Testing uses tests to help drive the direction of the code. That’s an important distinction to understand, because if the code is difficult to write or if it’s going in the wrong direction, typically the root cause of the problem is the test itself. Improving the tests that you write, also improves the code that you end up with.

Corey Haines has an amazing Roman Numerals code kata in Ruby, where his commentary reveals some amazing insights about the process. Find 15 minutes to sit down for this video and you won’t be disappointed.

What techniques does he mention?

Also found separately, a good piece on giving up on TDD with an unexpected lesson.

I’m glad that these issues are being delved in to during practice time. I don’t think that I could deal with them when I’m actually expected to do something instead.

Hey look at that, it’s lunchtime. And I haven’t even started programming yet :slight_smile:

1 Like

Making the first test pass

According to the Getting started page on Jest, we can start with a simple test file. In this case, I will test that the fizzbuzz function does nothing when it’s called without a number.

fizzbuzz.test.js

const fizzbuzz = require('./fizzbuzz');

test('does nothing with no argument', () => {
  expect(fizzbuzz()).toBeUndefined();
});

Immediately before I run the test I see that my code editor has linting issues with the code. First I’ll get a passing test, and then I’ll work on improvements.

Running npm test from the command prompt, tells me that it can’t find the ./fizzbuzz file, so I create that.

Running the test again, I’m told that fizzbuzz is not a function. So let’s create a function.

fizzbuzz.js

module.exports = function () {

};

And the test passes. I can now make improvements while … no hang on. I must commit this working code first, before moving on.

Commit the working code

Doing a commnand of git status shows me that there’s hundreds of files in the node_modules/ folder.
Some people say to add them, others say don’t add them. I’m going to not add them, because I know that npm install will recreate the node_modules folder.

The proper way to ignore something with git, is to create a .gitignore file with the files/folders that you want to ignore

.gitignore

node_modules/

Now with doing git status I only see a few files that haven’t been added. All those files are relevant, so let’s add them.

get add *.*
git commit -m "Initial test"

Do I push the commit to the server? No, not just yet. I want to learn more about whether I should push to the master, or to a separate branch first. According to A successful Git branching model you push to a separate branch called develop, which is a great idea.

git branch develop
git push origin develop

I can now go on with making improvements while checking that the test continues to pass.

Improvements once the test passes

There are some improvements that I want to make to the testing process before carrying on with writing more code. I want:

  • to be able to trigger the testing from within my code editor.
  • to see the test result while I stay in my code editor.
  • to fix those lint errors in my code before carrying on
  • to use LiveReload so that changed files result in automatic reload of my web browser page

Trigger testing from within code editor

When I tell my code editor that I want to use a build system, I find out that I don’t have it set up with a project yet, so I create a project and then tell it about the new build system that I want to use, according to the instructions at Sublime 3 build system to run NPM Test

Those instructions have issues. The fancy curly quotes must be converted into normal double quotes, resulting in the following build instruction:

npm.sublime-build

{
  "working_dir": "${project_path:${folder:${file_path}}}",
  "cmd": ["npm", "test"]
}

But, when trying to use it, I’m told that it can’t be found:

[WinError 2] The system cannot find the file specified
[cmd: ['npm', 'test']]

Renaming it to npm.cmd solves that problem,

npm.sublime-build

{
  "working_dir": "${project_path:${folder:${file_path}}}",
  "cmd": ["npm.cmd", "test"]
}

Conveniently, when I now test the code from within my text editor, with just Ctrl+B, I can immediately see the test results too.

While the build works, I don’t like how it’s all in black and white. I want color.
I’ve tried using an ANSIEscape package and configuring the build to use that, but I don’t know how to force npm to output in ANSI color when it thinks that it shouldn’t, so another option must be used instead.

Having the test always watch for changes

Good bye build, and hello command prompt. I can run jest and have it watch for changes so that it reruns the tests automatically, by using the --watch command.

node_modules\bin\jest --watch

Now, I can have that command window in the lower part of the screen, and saving the file has the test run immediately.
Previously with the build it would take 3.6 seconds to run the test. Now it takes only 0.28 second for the test.

I also want the command window to remain on top instead of going under any other window. That way the test will be always visible.

Thanks to the Make a window stay Always On Top in Windows 10/8/7 article I have my pick of techniques to use. I’m going to try TurboTop today.

With that, I can choose the watching test window, and now when I save my file the test now automatically runs, is always visible, and is in glorious color!

Fixing lint errors

I can now focus on fixing linting errors in my code editor, which will remove annoying red boxes around each of those errors, resulting in code that looks like:

fizzbuzz.test.js

/*jslint browser */ /*global test expect */
(function iife() {
    "use strict";

    const fizzbuzz = require("./fizzbuzz");

    test("does nothing with no argument", function () {
        expect(fizzbuzz()).toBeUndefined();
    });
}());

and
fizzbuzz.js

"use strict";
module.exports = function fizzbuzz() {
    return;
};

The last thing that I wanted to do was to use LiveReload, but it’s now time for afternoon tea so I’ll come back to this after that.

1 Like

Fizzbuzz testing with Jest and LiveReload

LiveReload

I tried out LiveReload yesterday and found that you need both the program to tell it which folders to watch, and the browser extension to control the reloading of the page. It doesn’t work on local file access either, only on localhost, so setting up a local server is required too.

I already have LiveReload installed so I just need to run it and add the fizzbuzz folder.

Setting up a local server should just involve installing serve as a development dependency:

npm install --save-dev serve

I can now create a local server on localhost:5000 that points to the current directory, by running the following command:

node_modules\.bin\serve

And http://localhost:5000/ shows me my fizzbuzz files, and enabling the LiveReload extension causes the page to refresh whenever I change a file. Sweet.

Let’s get a simple index.html file in place:

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Fizzbuzz 2017-12-28</title>
</head>
<body>
<h1>Fizzbuzz</h1>
</body>
</html>

Now to get the script involved there too.

Browserify to simplify bundling up the files

While exploring several different options to see the results on the webpage when I save and test the code, I came across Browserify, and while separately looking for information about Browserify, I came across the following Sitepoint article called Getting Started with Browserify. It’s a small world.

I’m told to install it globally, but I don’t think it should be available from everywhere, so I’ll install it as a production dependency instead.

npm install --save-dev browserify

I can now run browserify manually, with:

node_modules\.bin\browserify fizzbuzz.js -o bundle.js

But I want it to be easier to do each time. I can put it in my package.json file as a build script:

  "scripts": {
    "test": "jest --watch",
    "build": "browserify fizzbuzz.js -o bundle.js"
  },

and then more easily run it with the following command:

npm run build

The resulting bundle.js file I can now add to my index.html file too:

<body>
...
<script src="bundle.js"></script>
</body>

But I also want to build from my code editor, so I can replace the existing npm build script with:

{
  "working_dir": "${project_path:${folder:${file_path}}}",
  "cmd": ["npm.cmd", "run", "build"]
}

and now pressing Ctrl+B from my code editor, builds the code.

Connecting fizzbuzz to index.html

We’re now compiling fizzbuzz.js but we aren’t able to use it yet. That’s what index.js is for.

index.js

var fizzbuzz = require("./fizzbuzz.js");
fizzbuzz(100);

We can now update package.json to build with index.js instead:

package.json

    "build": "browserify index.js > bundle.js"

and then from my code editor I can build the code.

But how do I tell that it’s actually working? I can temporarily update the fizzbuzz() function to output something to th web page:

fizzbuzz.js

/*jslint browser */
module.exports = function fizzbuzz() {
    "use strict";
    document.body.innerHTML = "test that function has run";
    return;
};

And now when I build the code, reloading the page at http://localhost:5000/ shows that the innerHTML does add the text on the page.

I can remove that document.body line now, knowing that the plumbing is all working well.

Improving the build process, with watchify and LiveReload

I don’t want to manually build the project every time, and manually reload the page.

From the Getting started with Browserify article I find out about Watchify which automatically runs the browserify command when files are saved.

Watchify is installed with the following command:

npm install --save-dev watchify

and is added to my package.json file beside the build script with:

package.json

 "scripts": {
    "test": "jest --watch",
    "build": "browserify index.js -o bundle.js",
    "watch": "watchify index.js -o bundle.js"
  },

When I save a file the bundle.js file looks like it gets updated, but the command prompt window that’s running watchify doesn’t say anything. I’ll add a verbose flag to the package.json script.

package.json

    "watch": "watchify index.js -o bundle.js -v"

And now the command prompt window tells me what it’s doing, with:

709 bytes written to bundle.js (0.02 seconds) at 5:23:28 PM

With LiveReload going, I now see the changes automatically occur in my browser too.

So now, I still haven’t written much in the way of test or actual code, but I do have a very nice development setup with three separate command prompt windows running, doing:

  1. Jest rest results
  2. local server supplying localhost:5000
  3. auto-building with browserify

and, LiveReload which triggers reloading of the web page.

I’m all ready to go but it’s now dinner time. I’ll be able to get some actual test-driven development done when I return.

1 Like

I can now start testing and developing the basic features of fizzbuzz.

The first real test

To make the fizzbuzz() function easy to test, I don’t want to deal with HTML formatting codes when doing the testing. One option is to have fizzbuzz return an array of values, and another option is to have it use “\n” to separate each number. I’ll go with the array option here.

fizzbuzz.test.js

    test("gives 1 when given 1 as an argument", function () {
        expect(fizzbuzz(1)).toEqual([1]);
    });

The simplest way to make that pass, keeping the transformation priority premises in mind, is to use an if statement to return when there’s no number, and to otherwise return an array that contains [1]

fizzbuzz.js

module.exports = function fizzbuzz(n) {
    "use strict";
    if (!n) {
        return;
    }
    return [1];
};

Because the test code is quite obvious (as it’s in a test() function), I’ll stop putting the filename with each section of code.

We want the web page to display that array on the screen, so the index.html page needs a place to put the results:

<h1>Fizzbuzz</h1>
<div class="result"></div>

and the index.js file needs to get that results section, and add the fizzbuzz numbers to the page.

var fizzbuzz = require("./fizzbuzz.js");
var result = document.querySelector(".result");
result.innerHTML = fizzbuzz(100).join("<p>");

And, the browser page automatically updates showing 1. It’s a start.

Test for 2

When giving the fizzbuzz() function an argument of 2, we expect to get an array that contains [1, 2]

    test("gives an array of [1, 2] with 2 as an argument", function () {
        expect(fizzbuzz(2)).toEqual([1, 2]);
    });

That gives is a properly failing test, and the code to make the test pass is:

    if (n === 1) {
        return [1];
    }
    return [1, 2];

It looks silly I know, but now we can refactor to better code, with all of the tests still passing too.

The array is needed for all valid inputs, so that can be created at the start, with values being added to it.

    var arr = [];
    arr.push(1);
    if (n === 1) {
        return arr;
    }
    arr.push(2);
    return arr;

I can tell that we’re going to need a while loop here, but it’s not time yet, for more examples of duplication will be required.

The Fizz test

    test("gives an array of [1, 2, \"Fizz\"] with 3 as an argument", function () {
        expect(fizzbuzz(3)).toEqual([1, 2, "Fizz"]);
    });
  • I don’t like escaping the quotes around Fizz in the test because it’s messy.
  • Single quotes around the test text lets me use double quotes, but single quotes go against the expected standard of double quotes for strings
  • I could us single quotes around Fizz, but then the test output is non-standard too.

Instead, I’ll use the backtick for ES6 template strings, which lets me use single and double quotes inside of the strings without any other trouble.

   test(`gives an array of [1, 2, "Fizz"] with 3 as an argument`, function () {
        expect(fizzbuzz(3)).toEqual([1, 2, "Fizz"]);
    });

The code to make this pass now shows three sets of duplication:

    var arr = [];
    arr.push(1);
    if (n === 1) {
        return arr;
    }
    arr.push(2);
    if (n === 2) {
        return arr;
    }
    arr.push("Fizz");
    return arr;

So with three examples of duplication, it’s now a good time to do something about it.

According to the transformation priority premise, a scalar is simpler than a while loop, so let’s change those 1 and 2 numbers into a scalar variable.

    var i = 0;
    i += 1;
    arr.push(i);
    if (n === i) {
        return arr;
    }
    i += 1;
    arr.push(i);
    if (n === i) {
        return arr;
    }
    arr.push("Fizz");
    return arr;

I have a few options here. I see from the transformation priority premise that recursion is simpler than a while loop, but I’ll leave recursion to a later session when I’m more familiar with the process of doing this test.

If I am to put this in a while loop, I need a consistent condition if condition that works with everything that we currently have here. I know from experience that it’s going to involve the modulus operator, but I want the tests to help drive me towards that.

For now, the first two can be put in to a while loop.
In fact, trying that results in one of the tests failing because it has 3 instead of Fizz, so I’m now forced to use a separate condition for Fizz.

    while (i < n) {
        i += 1;
        if (i === 3) {
            arr.push("Fizz");
        } else {
            arr.push(i);
        }
    }
    return arr;

The tests all still pass, but their text could be improved. While it’s easy to do I’ve renamed them to be more expressive on the test page, saying:

  √ does nothing with no argument (3ms)
  √ an argument of 1 gives [1]  (2ms)
  √ an argument of 2 gives [1, 2] (1ms)
  √ an argument of 3 gives [1, 2, "Fizz"] (1ms)

The Buzz test

The test for 4 passes automatically so there doesn’t seem to be much use for that here. Instead it’s straight on to 5.

    test(`an argument of 5 gives [1, 2, "Fizz", 4, "Buzz"]`, function () {
        expect(fizzbuzz(5)).toEqual([1, 2, "Fizz", 4, "Buzz"]);
    });

Making that pass is as easy as adding an else if clause for Buzz.

        if (i === 3) {
            arr.push("Fizz");
        } else if (i === 5) {
            arr.push("Buzz");
        } else {

Multiple Fizz tests

The test for 6 is where we’ll need to have more than one Fizz appearing in the result.

  test(`an argument of 6 gives [1, 2, "Fizz", 4, "Buzz", "Fizz"]`, function () {
        expect(fizzbuzz(5)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz"]);
    });

Using modulus in the code should make this one really easy to pass.

        if (i % 3 === 0) {
            arr.push("Fizz");

But it doesn’t pass. That’s unexpected.

Examining the test I see that it received an array with only 5 values instead of 6. Why would that be?

The test! Don’t lose focus when creating the test. I got lazy and needed to run the fizzbuzz() function with an argument of 6, instead of 5.

    test(`an argument of 6 gives [1, 2, "Fizz", 4, "Buzz", "Fizz"]`, function () {
        expect(fizzbuzz(6)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz"]);
    });

And the code now passes. Just to be sure though, removing modulus from the fizzbuzz() code makes it fail, and is passes when using the modulus technique.

Multiple Buzz tests

All tests work on up to 9 now, so 10 is our next target which should be another Buzz.

    test(`an argument of 10 gives [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]`, function () {
        expect(fizzbuzz(10)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]);
    });

That test description is getting too long now, so let’s only show the last 5 values of the expected array.

    test(`an argument of 10 gives [..., "Fizz", 7, 8, "Fizz", "Buzz"]`, function () {
        expect(fizzbuzz(10)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]);
    });

Making this one pass should be easy now too.

        } else if (i % 5 === 0) {
            arr.push("Buzz");

and it passes easily.

The last Fizzbuzz hurdle

With 15 we find that we reach the last problem. What do we want to occur when both Fizz and Buzz are valid?
Do we want them to be combined together to form FizzBuzz, or do we want it to be just a single word of Fizzbuzz?

I’ll go with FizzBuzz for now, because we can always change it later on if we want to.

    test(`an argument of 15 gives [..., 11, "Fizz", 13, 14, "FizzBuzz"]`, function () {
        expect(fizzbuzz(15)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"]);
    });

and we can make it pass by using a combined if condition:

        if (i % 15 === 0) {
            arr.push("FizzBuzz");
        } else if (i % 3 === 0) {
            arr.push("Fizz");

I’m not happy with that code though. If we get asked to also do Bat on multiples of 7, the multiple checks will get complex quite quickly.

I want to remove those else clauses. They’re there so that we can put in a numeric value if none of the others are used, but we can achieve the same by starting with a string in which Fizz and Buzz are added, and checking to see if that string is empty at the end of things.

    while (i < n) {
        i += 1;
        str = "";
        if (i % 15 === 0) {
            str = "FizzBuzz";
            arr.push(str);
        } else if (i % 3 === 0) {
            str = "Fizz";
            arr.push(str);
        } else if (i % 5 === 0) {
            str = "Buzz";
            arr.push(str);
        }
        if (str === "") {
            arr.push(i);
        }
    }

By assigning i to str in the last if statement, we can now remove all of those push statements and have only one push at the bottom.

    while (i < n) {
        i += 1;
        str = "";
        if (i % 15 === 0) {
            str = "FizzBuzz";
        } else if (i % 3 === 0) {
            str = "Fizz";
        } else if (i % 5 === 0) {
            str = "Buzz";
        }
        if (str === "") {
            str = i;
        }
        arr.push(str);
    }

And now, the rest of the else clauses can be removed, with the 15 check moving to the end.

        if (i % 3 === 0) {
            str = "Fizz";
        }
        if (i % 5 === 0) {
            str = "Buzz";
        }
        if (i % 15 === 0) {
            str = "FizzBuzz";
        }

The tests all still pass, and we can now remove that 15 check completely, by adding to str instead of replacing it.

That leaves us with the following code in the while loop.

    while (i < n) {
        i += 1;
        str = "";
        if (i % 3 === 0) {
            str += "Fizz";
        }
        if (i % 5 === 0) {
            str += "Buzz";
        }
        if (str === "") {
            str = i;
        }
        arr.push(str);
    }

Going beyond

As a check on the usefulness of this technique, I’ll add a test for multiples of 7 which means changing the others tests, so that 7 becomes “Bat”

    test(`an argument of 10 gives [..., "Fizz", 7, 8, "Fizz", "Buzz"]`, function () {
        expect(fizzbuzz(10)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", "Bat", 8, "Fizz", "Buzz"]);
    });
    test(`an argument of 15 gives [..., 11, "Fizz", 13, 14, "FizzBuzz"]`, function () {
        expect(fizzbuzz(15)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", "Bat", 8, "Fizz", "Buzz", 11, "Fizz", 13, "Bat", "FizzBuzz"]);
    });
    test(`an argument of 21 gives [..., "Bat", "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "FizzBat"]`, function () {
        expect(fizzbuzz(21)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", "Bat", 8, "Fizz", "Buzz", 11, "Fizz", 13, "Bat", "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "FizzBat"]);
    });

To make that pass, I only need to add a clause for the multiple of 7 in the code:

        if (i % 5 === 0) {
            str += "Buzz";
        }
        if (i % 7 === 0) {
            str += "Bat";
        }

and it’s all done and passing.

This brings the practice kata to a close, and I now also have a very nice development setup as a part of the process too, which can be used for future efforts.

2 Likes

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.