A Foray Into Testing Algos

In my practice with Algorithms, I tend to code in my own editor instead of the built-in editors on LeetCode and AlgoExpert simple because I like the ability to save many different solutions, have an all-in-one store of all the problems I’ve worked on, and because I like the ability to build certain features and tests out myself. And speaking of testing, this is something I have been meaning to get into for a while and I decided to start now because it was just the other day that I had grown to have enough of commenting and uncommenting my tests. It’s time I work to automate my testing and, if I’m being honest, I hardly looked into how it is ‘supposed’ to be done before writing this post so this will be a bit of mutual exploration.
I will show you what I am working with currently, briefly explain the problem and solution, and then craft some way to automate my tests so I don’t have to manually administer each test individually.
Let’s get started.

It’s kind of a mess and shows no real separation of concerns, even for a personal academic project.

Here we have an algorithm that takes in an array of ordered integers as an input and outputs an ordered array of integers where each value in the input is represented by that value squared in the output.
Each value in the input array is a whole number, as alluded to before, but not each number is positive. When a negative number is squared it becomes positive and the significance of that for us is that when we are working with an input array containing negative numbers, we will have to sort the resulting squared array before returning it as our output.

A pretty basic algorithm, but good because it contains some important methods.

This solution really only has two parts to it:

  1. Loop through the input array, multiplying the value of each index with itself, Math.pow(<your_value>, multiple), and push that new value to a variable to hold an array of new values, newArr.
  2. This last part, line 34, handles the case where we are working with negative numbers. Since the sort() method increases our solution’s complexity, we want to be sure to only use it when we need it, which is when we have a negative number in our input. And our input is ordered so we can simply just test to see if the value in the first index of our input array is negative, if(array[0] < 1), and if it is, then we sort the array we added all our new values to in the previous part, newArr.sort(a,b) => a-b.
In, out, simple.

The above example shows a case where we are given an array input, we loop through storing the squared values in a separate array and then return that separate array without needing to re-sort it.

O(n log n), the built-in sort will sort our new array in place.

The above example shows a case where we are given an array input, we loop through storing the squared values in a separate array and then sort that array before returning it because our solution’s conditional returns true when it tests the first value in the input array and finds that -5 is less than 0.

Lines 57 & 58 for the previous example.

Now here are all of our tests which you may recognize. In the previous two examples, I would only leave one uncommented at a time to avoid a mess of numbers in my console and I am manually looking to see if the answer Node returns match the answer below each test. Not efficient and very time-consuming when you have a lot of tests and many edge cases, so let’s automate this.
The first thing I want to happen is for all the tests to be executed each time I run my file with Node. This means I need to create a function to hold all of my tests.

There’s a typo, can you find it?
  1. Here we create a function that wraps around all of our tests, and when we run this file with Node this function will execute all our tests on our sortedSquaredArray function.
  2. This is our return. Doesn’t tell us whether it’s correct or not, or even which test the output belongs to.

So far we haven’t simplified anything, we’ve only added 3 lines of code: the function wrapper, the end of the block containing all of our tests, and the call to that test function. Well, what if we created a helper function that takes in our test input and compares it against our expected output, which we already have commented below each test? Let’s try it.

Your DRY alarm should be going off. Don’t repeat yourself.

Ok, so a lot’s changed in this picture so let me go over what happened step-by-step because it really is pretty simple.

  1. I created a function runTest. It will return whether sortedSquaredArray returns the expected value.
  2. runTest will take the input array we want to test as its first argument and the array we expect to be returned and which we previously had commented, as the second argument.
  3. Here we create the function runTest and label the parameters appropriately, input (the array we are testing which will be passed to sortedSquaredArray), and expectedOutput (what we expect to be returned if our solution, sortedSquaredArray is correct).
  4. The runTest function will call our sortedSquaredArray function and store the result in a variable actualOutput. Then we compare actualOutput variable to the expectedOutput argument and return a value conditionally on whether the values match up. That conditional return will be logged to the console.
  5. Not all of our tests have passed.

It’s important to note here that arrays are stored by reference and as such, each array is unique. That means we have to convert them into something else, like strings, before we can compare.

By storing out tests in an Object they can be referenced with little complexity and identified using the key.

Not bad to start but there are quite a few improvements I think we can make for readability among other things.
First off, we repeat console.log(runTest(input, expectedOutput); many times over and it’s rather hard to read so I’m going to clean that up.

  1. I’m going to put all of our inputs and outputs into an enumerable list. I chose to use an object over an array for this to reduce complexity and because we have a variable number of tests. That is we may add more in the future and it doesn’t matter what order the tests are executed in. Additionally, we can use the key to reference which test produced a particular result.
  2. Here I just test that I am getting the desired output. That is, the message will reference the unique key of the ‘tests’ object as a reference to the test that produced a result and it will also log the actual contents of that test.

Now that we know everything works so far, what we should do is pass the appropriate values to our runTest function. Additionally, I will pass the key in as the third argument to runTest so I can reference it in my error message.

  1. We replace the console.log() with a call to our runTest function, passing in the parts of our object, as we enumerate through it. Additionally, I added a third argument which is the object key that I will use as an ID to identify which test we are currently on. We are getting mostly the same information passed into our runTest function so I really only had to alter the messaging to reflect the ID of the test that is currently being performed.
  2. We can now see a console log of each test by its ID, whether it passed the test, and why it failed, if applicable.

And I’m happy with where the tests are at now. There are still more things we can do to make this neater such as bringing in frameworks like LoDash or Jest or even just separating the tests out into another file and importing all the different versions of our solution in that test file. Then we can test everything out at once, give everything its own speed test, or do whatever else we can imagine. And don’t think I won’t… next article, haha!
Thanks for reading and as always, please feel free to reach out with your thoughts, I’m still figuring things out with best practices and such and I would love to hear your input and thoughts.

Frontend Software Developer and Security Technician with experience in Ruby, Rails, JavaScript, and React. Flatiron Software Engineering Alumni.