Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Three Ways to Improve Your Code Testing

DZone's Guide to

Three Ways to Improve Your Code Testing

I am of the firm belief that one of the best ways to ensure short term and long term code quality is to make sure there that your tests are exercising as much of the code base as possible, as often as possible. Here are a few tips to help you improve your code testing.

· DevOps Zone
Free Resource

Download “The DevOps Journey - From Waterfall to Continuous Delivery” to learn learn about the importance of integrating automated testing into the DevOps workflow, brought to you in partnership with Sauce Labs.

3-Ways-to-Improve-Your-Code-TestingI am a big fan of Test Driven Development (TDD). I drank the kool-aid a while back and have not had a regret since. When I sling code, I am always writing a test, or writing against a test. That’s how I’m built.

I am of the firm belief that one of the best ways to ensure short term and long term code quality is to make sure there that your tests are exercising as much of the code base as possible, as often as possible. Thus, I am always looking for ways to improve my test coverage and test frequency. Again, the more code I can exercise when testing, the better it is.

Bonus Randomization Utility is at the End!

At the end of this article, you will find a link to a NodeJS module I wrote and posted on NPM. The module is h2randomizer. h2randomizer is a utility that publishes methods that return random data in a variety of ways. You can get a random first name, last name or an address based on actual city zip code. These are but few of the methods.

Hard Coding… Bad

One of the test techniques I use consistently is data randomization. It is rare for me to write a test with hardcoded values. Yet, sometimes I do and I am being bad. Take a look at this following snippet of NodeJS code in Listing 1:

{
.
.
  let numToRun = 4;
  let users = [];
  for (let i = 0; i < numToRun; i++) {
     let aUser = testHelper.getRandomApplicationUser();
     users.push(aUser)
  }
  users.forEach(function(user){
      processUser(user, function(err){
           assert.IsTrue(! err);
        });
   });
.
.
}
Listing 1: Code that creates an array and then executes a function on each item in the array


Listing 1 illustrates code that creates an array named users and populates that array with user objects. A user object is created using a custom method, getRandomApplicationUser(). The method, getRandomApplicationUser() is one I created that returns a user object. The user object is assigned data to its properties randomly. Then, each member in the array, users, is passed as a parameter to a method, processUser(). The method, processUser() does its thing on the user object. If there is a problem, the callback function will report the error, err. Checking for the absence of an error is the objective of the test assertion.

I have avoided the hazards of hard coding test data because I created each user object with random data. So, all should be good. Right? Wrong! Here’s why:

let numToRun = 4;


The statement above is pretty evil stuff. Why? Because the only thing I know for sure is that my method, processUser() works for an array with 4 user objects in it. That’s it. What would happen if I passed an array with 5 user objects. What about zero user objects? What about 1000 user objects? Dunno. All I know is that the test passes for 4 user objects and that’s that. Now, if the use case that my code needs to support is an array of 4 user objects, I am good. But use cases that constrain array size to a constant value at run time is more the exception than the rule.

Obviously, I have not eaten my own dog food in its entirety. Yes, I randomized user data, but I did not randomize the number of user objects to process. Nor, did I follow the standard practice of testing the edges of ranges. At the least, I should have tested an empty users array. Also, I should have tested beyond typical anticipated usage, say for 1000 user objects, as I mentioned above.

Testing Edge Cases

So, what should my test look like? My test should not be a single test, but a number of tests as follows:

  • Empty array test
  • General randomization test
  • Upper edge case randomization test
  • Lower edge case randomization test

Let’s revisit the scenario. Let say that my lower edge case is any number below 10 and my upper edge is any number above 100. First, I encapsulate my test into a single method that takes as a parameter the number of users to create. (Please see Listing 2.)

function myUseCase(numToRun){
 let users = [];
 for (let i = 0; i < numToRun; i++) {
   let aUser = testHelper.getRandomApplicationUser();
   users.push(aUser)
 }
 users.forEach(function(user){
   processUser(user, function(err){
     assert.IsTrue(! err);
   });
 });
}
Listing 2: Encapsulating a test case into a single method


Then, I’ll use a randomization function I lifted from the randomization library I provide at the end of this piece. The randomization function is randomizer.getRandomIntInclusiveSync(min, max).randomizer.getRandomIntInclusiveSync() allows you to get a random value between a minimum and maximum value, like so:

let num = randomizer.getRandomIntInclusiveSync(1, 100)


Once I get the test encapsulated into a function, it’s just a question of generating the run count. So the test scenarios described above are as follows:

Empty array test

myUseCase(0)


General randomization test, within expected bounds between 10 and 100

for(let i = 0, i<1000,i++){
  myUseCase(randomizer.getRandomIntInclusiveSync(10, 100));
}


Upper edge case randomization test, with the upper edge starting at 100

for(let i = 0, i<20,i++){
  myUseCase(randomizer.getRandomIntInclusiveSync(100, 200));
}


Lower edge case randomization test, with a maximum of 10

for(let i = 0, i<20,i++){
  myUseCase(randomizer.getRandomIntInclusiveSync(1, 10));
}


You might be wondering why I am executing tests within a for{} loop. The reason is, again, one of randomization. Of course, using a single random value as run count for my tests is a whole lot better than just using a hardcoded run count. However, running many random run count values adds a higher degree of code exercise. Remember please, the goal is to have your code tested in a variety of ways, all the time, if possible. Of course,  it is not possible. Most of the time, yes. All of the time, no. But, we can make each testing session high volume. Thus, the for{} loops.

Testing For All Possible Values

Applying random values is useful, no doubt. However, I can take a more rigid approach and just test for all values, from the lower edge to well above the upper edge. I do this sort of test like so:

for(let i = 0; i<200; i++){
  myUseCase(i);
}


Exercising the code from the lower to upper edge is a viable approach. The only risk I run is that the code might behave very nicely when the run count parameter value is applied in sequence. Yet the code might be completely out of whack when the run count value is applied using random values. It’s a matter of use case.

Putting It All Together

So, what’s the takeaways? As promised there are 3.

  • If you can avoid it, do not hardcode your test values.
  • Randomize.
  • Always write tests for the edge cases.

There you have it.

Oh yes, the Bonus NodeJS Randomization Utility. You can get it here.

Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure, brought to you in partnership with Sauce Labs

Topics:
code ,test ,driven ,values ,development ,general ,test driven development ,nodejs

Published at DZone with permission of Bob Reselman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}