Simple Reasoning about Unit Testing — Part 2 (Tutorial using JavaScript)

We will be doing a simple re-write of the Math.pow() function in JavaScript, not complete just enough to show how testing could be done and what problems could be encountered.

Link to Part 1 discussing some benefits of testing: https://medium.com/@nsheikhmohammed01/simple-reasoning-about-unit-testing-5efe1cc9db0b

Let’s Begin…

Step 1: Thinking about the problem…

  • The problem we want to solve is raising a base, b to an exponent, e. The mathematical form being 10³. 10 being the base and 3 being the exponent.
  • We want to also define the scope of the problem, So for this example, we won’t be dealing with negative exponents.
  • Edge cases: If the exponent is 0, we return a 1. If the exponent is 1, we return the base. If the base is 0 (Maths dilemma: The answer is actually undefined but we don’t want to return undefined because it means something in programming), we want to simply return a 0.

Step 2: Nuances of the language…

  • JavaScript is a dynamically typed language, therefore the user can decide to pass any data type (string, arrays) into the function. For this case, we will only accept numbers, anything other than that we simply pass an error message telling the user of the accepted type.

Step 3: Write the tests and then the code…

We’ll be using Jest framework to do our testing and ES6 syntax for JavaScript code.

  1. Create a folder called Pow-Testing and open it in your editor of choice
  2. Fire up the ol’ terminal and navigate to the Pow-Testing folder
  3. In the terminal, run:
"npm init -y" // to create a package.json file

4. You should have a package.json file in your folder, open it and check under the scripts object, there should be a line that goes:

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

Replace the line with the following:

"test": "jest"

This helps us to run our test

5. Now to install the Jest framework, go to the terminal and run

npm i jest

6. In order to write ES6 we need to setup Babel to transpile our code else node doesn’t know how to compile the code. In the terminal, run

npm i -D babel-jest babel-preset-env

7. Create a “.babelrc” file and write the following inside

{
"presets": ["env"]
}

Finally, done with configuration.

Now for the fun part

  • Create a file power.js, this file will contain the code that is to be tested.
  • Create a folder named test in the project and a file called index.spec.js inside the newly created test folder.
  • The project structure should look something like this:
Pow-Testing
|
|_ package.json
|
|_ power.js
|
|_ .babelrc
|
|_ test
|
|_index.spec.js

In your power.js file, write the skeleton code of the function to be tested:

Now to start writing the tests:

In the index.spec.js file,

  • we want to import the function from power and set up the test spec
  • Type the following into the index.spec.js file

The describe block in the code will house all the tests, it takes two arguments, a string (describing the overall scope of the test cases to be defined inside it) and a callback function (houses all the tests) written using the arrow function syntax for brevity.

Running the test command from the terminal using the “npm test” command should yield an error saying that the spec needs to contain at list one test. So let’s write our test.

Remember, the guideline we are using, is to write one test, watch it fail, then write the minimum amount of code for it to pass the test and then repeat the process until you’re done with the code.

In order to define a test, we use the it function which takes two arguments: a string, describing what you’re testing for and a function encapsulating the test. Again, we use the arrow function for brevity.

For the testing, we simply use an expect function with the code to be tested, in our case, the power function that was imported. It is then chained to a assert function to check if the value is correct. We will be using 2 assert functions: toBe() used to check for numbers and toEqual() used for comparing strings.

// it function signature
it('test description', () => {});
// expect function with asserts
expect(tested(...args)).toBe()

Test Case 1:

  • We want to check the types of both arguments passed into the function, if either of them is not a number, we return a message to the user saying only numbers are allowed
  • In index.spec.js, type:

In the terminal, run npm test , this should produce an error message telling you that the expected value and the received value aren’t the same. No suprises there, we expected the test to fail. Then we’ll write the code that passes the test.

From now on, we’ll be running the tests a lot, so we’ll set up package.json to watch our test file for us and update, while we focus on writing our code.

In package.json, under the scripts object, the test property should be updated to:

"scripts": {
"test": "jest --watchAll"
}

After the update, run npm test in the terminal and it will run the tests for us and update as we change and save our files.

Pass the test

In our power.js file, we write the code that passes the test

Looking at the terminal, we see the green message of success, a feeling of satisfaction should overwhelm you right about now. We carry on.

The Remaining Tests…

We move on to our edge cases, Lines with *TEST* are in the index.spec.js file, the code is to be inserted after the last it function and lines with *PASS* are in the power.js file, the code should the inserted as the last line before the end of the function.

// In index.spec.js
describe('', () => {
it(...){
}
// insert *TEST* code here
})
// In power.js
function power(...) {
...
//insert *PASS* code here
}
  • CASE: Testing for exponent less than 0, should return a message, saying only positive exponents are allowed
  • *TEST*
  • *PASS*
  • CASE: Test if exponent is 0, return 1
  • *TEST*
  • *PASS*
  • CASE: Test if base is 0, return 0
  • *TEST*
  • *PASS*

ERROR, TEST FAILED

Order Matters !!!

Hey ! What happened ??? I just wrote the last passing code, but the test still failed. Well, that’s because of the ordering of the code. When we were brainstorming the problem, we decided that if the base is 0, the returned value should be 0 but if you check your test error message it says it received 1 as the result.

During testing you can have two valid test cases that cross each other out and the order in which you write the passing code could affect your result and introduce bugs. One important property of tests is that they should be independent of each other, it shouldn’t matter how I call the code, the result should be correct.Another important concept to note here is, if the new code you write is failing tests, always fix errors before moving on. Don’t introduce bugs and leave them unattended.

Fixing the previous bug

  • *PASS*: Switch the order of the last 2 statements

Now the test should pass, you can even switch your it functions that test both the exponent and base being 0 in the index.spec.file and see that it doesn’t matter.

Conclusion and Tips

We’ve gone through how to brainstorm problems, define the scope of the solution and set up tests before writing code. We also discovered how some tests could interfere with each other if they aren’t ordered properly.

The tests were not rigorous but they showed how they can affect the quality of code and the depth of thinking about the problem before diving head first into code.

I’ll leave the rest for you to complete. The complete code will be on github: https://github.com/nursh/JS-TESTS

Useful Tips

  • Think about the problem and how to handle edge cases and errors
  • Understand the nuances of the language you’re using and compensate for it
  • Write your test
  • Watch test fail
  • Write passing code
  • Refactor
  • Repeat steps 3–6 until done
  • Watch out for tests that cancel out due to code ordering
  • Always fix errors before moving on with your code

Hopefully you learnt something from this short tutorial.

Like what you read? Give nuradeen sheikh a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.