I'm very excited to start sharing with all of you, my five best tips to being a better programmer. These tips are ones that I used on my journey to becoming a senior level programmer as quickly as I did. For those of you who are interested, here in Canada, the absolute minimum amount of time it takes a programmer to progress to a senior level is five years. I was able to attain my title in five years through hard work, and smart work. I know you're all hard workers, but it's time to arm you with some killer knowledge to help give you a strong start!

My first tip revolves around ensuring you have quality code. When creating applications, it's of paramount importance that you keep the number of bugs as close to zero as possible. Now, I say as close to zero as you can, because I realize that once your application grows in size, it's very difficult to have absolutely no bugs. But with this tip, you will certainly be on your way to producing top notch code.

So, how could I possibly increase the quality of your code with just one blog post? Well friends, it's all about tip #1, unit testing!

What is Unit Testing?

Unit testing is a way to automate an entire suite of test cases that can be run over and over again in quick succession to ensure that your code is producing expected results. It's a way to break your application up into its most important parts, and test them all individually to ensure that every part is working as it should be!

Does that sound simple enough? If not, then no worries, I have some examples for you :) Let's go back to our example of creating an application that will allow users to login if they enter the proper credentials. The first thing I would do when considering the fact that I wish to unit test a piece of code, is to think about all of the test cases for a particular part of the application.

Here are our requirements

1) A User will be allowed to login to the system when they provide a valid username and password combination.
2) If a username is entered that does not exist in the system, and error message must be shown that will alert the user that the username entered could not be found.
3) If a username is entered that is valid, but the corresponding password does not match what's on file, then an error message should be displayed explaining that the password is incorrect.

The Application Code

Okay, so now that we have our requirements, let's see some potential code for logging into a system:

package com.howtoprogramwithjava.business;

import java.util.ArrayList;
import java.util.List;

public class Login
{
  public boolean isValidUsername(String username)
  {
    List users = getUsersStoredInSystem();

    // this is called a 'for each' loop, it's
    // similar to a regular for loop, except that
    // it's easier to write, since you don't need
    // to specify an index to start at and end at.
    // it will just loop through all of the items
    // in the 'users' list.
    for (User aUser : users)
    {
      if (username.equals(aUser.getUsername()))
      {
        return true;
      }
    }
    return false;
  }

  public boolean isValidUsernameAndPassword (String username, String password)
  {
    List users = getUsersStoredInSystem();

    for (User aUser : users)
    {
      if (username.equals(aUser.getUsername()) && password.equals(aUser.getPassword()))
      {
        return true;
      }
    }

    return false;
  }

  public List getUsersStoredInSystem ()
  {
    List userList = new ArrayList();

    // create a list of 10 users, each with user names
    // that increment (ie. user1, user2, user3, user4),
    // all these users will have the password set to
    // 'howtoprogramwithjava'
    for (int i=0; i<10; i++)
    {
      userList.add(new User("user"+i, "howtoprogramwithjava"));
    }

    return userList;
  }
}

Okay, so here we have a Class called Login that defines three methods:

  • isValidUsername
  • isValidUsernameAndPassword
  • getUsersStoredInSystem

These three methods will be used to satisfy our requirements. Remember, we must check to see IF a username exists, as well as check to see if a combination of username and password is valid. So, given the code above, how certain are you that this code will work?

Well, the common approach is to test this code by manually typing in some usernames and passwords until something doesn't work right? Well, what if you have 6 different tests that you perform, and on the 4th test, something doesn't work. You would probably find out why it didn't work and then test again right? Well, are you going to re-test every one of the scenarios you already tested before you hit the test case that didn't work? You might, but what happens if the number of total test cases isn't 6, it's 600!

Imaging attempting almost 600 test cases manually, and then finding a bug on test #385. You could easily fix the bug, but now you'll have to go back and test 384 cases before you're confident you didn't break anything else!

In the real world, this kind of testing just doesn't happen and this is why some applications you've used had bugs in them. It happens people, and it's not always pretty! Now, wouldn't it be nice if you could just push a button, and all 600 tests could be performed automatically? Of course it would be great!

This isn't a dream, it's reality. It's called unit testing. So let's see how you could go about pulling it off!

JUnit

JUnit is a framework that allows you to create automated test cases that can be run at the push of a button. Now isn't that fancy! Great, you're sold right? Of course you are, so let's see what some unit test code looks like. The first thing we need to figure out, is what our test case should be. Let's take a look back at our requirements:

1) A User will be allowed to login to the system when they provide a valid username and password combination.

Okay, so if a User should be allowed into the system with a valid username/password combination, then that's a great test case to start with. What is a possible combination of username/password that should allow us into our system? Well, since we have a list of 10 valid users (as per our application's code) we should use one of those... I'll randomly choose to test:

username: user3
password: howtoprogramwithjava

Let's see what the code looks like to test this scenario:

package com.howtoprogramwithjava.test;

import static org.junit.Assert.assertTrue;
import org.junit.Test;
import com.howtoprogramwithjava.business.Login;

public class LoginTest
{
  // the variable name 'sut' stands for
  // system under test.  This is just a
  // coding convention and it refers to
  // the Object we will be testing.
  Login sut = new Login();

  @Test
  public void testValidUsernameAndPasswordCombination ()
  {
    boolean result = sut.isValidUsernameAndPassword("user3", "howtoprogramwithjava");
    assertTrue(result);
  }
}

Okay, so this doesn't look too different from what we're used to. Some interesting things to note are the use of the code @Test and assertTrue(). So let's talk about this stuff!

@Test

This is called an annotation, and it's used to 'mark' the method with some extra functionality. When we add this @Test annotation to our test method, it will tell JUnit to run our method (kind of like a public static void main() method). The big difference here, is that your test class (LoginTest) can have many test methods with the @Test annotation. This means that we can run several methods one after the other in rapid succession! Nice!

assertTrue()

One other important aspect to unit tests, is that we need to tell Java what it is that we expect to happen once the test is done. So in the case of our test method above, we're testing the case that a valid username and password is given. We would expect that when we invoke the isValidUsernameAndPassword method with a valid username/password combination, we would get a result of true right? So since we are expecting this method to return true we just insert the code assertTrue(result) to say that we want to check and make sure that the result is actually true. So, this would imply that if the result was false, we would get some kind of error right? Right! That's exactly what would happen... and JUnit likes to represent this whole 'correct test results' vs. 'incorrect test results' with green vs. red colours!

So without further delay, let's actually RUN our test!

How to Run the Test

This process is very similar to the process of running your programs. All you need to do is:

1. right click on either the test method or the test Class name
2. Choose Run As
3. JUnit Test

If all goes well, what you will see is a green bar appear that looks something like this:

Unit Testing

So like I said, it will be a green bar if all your tests were successful, and it will turn red if any weren't successful. Also, if there were any that didn't work, it will output the reason why it didn't work. So, let's expand on our test Class and add more test cases based on our requirements:

2) If a username is entered that does not exist in the system, and error message must be shown that will alert the user that the username entered could not be found.

So, the test case we should create here is to pass in an invalid username into our isValidUsername method:

package com.howtoprogramwithjava.test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import com.howtoprogramwithjava.business.Login;

public class LoginTest
{
  Login sut = new Login();

  @Test
  public void testValidUsernameAndPasswordCombination ()
  {
    boolean result = sut.isValidUsernameAndPassword("user3", "howtoprogramwithjava");
    assertTrue(result);
  }

  @Test
  public void testValidUsername ()
  {
    boolean result = sut.isValidUsername("user231");
    assertFalse(result);
  }
}

So, as you can see, we have just added a second unit test to our LoginTest Class. This one invokes the isValidUsername method and passes in an invalid username (in this case I randomly chose 'user2834'). This time we use the assertFalse piece of code to say that we are expecting this method to return a value of false. We are expecting this, because we are passing in a username that does NOT exist!

When the test cases are run, you should still see a green bar! So far so good.

Negative Tests

One other important aspect to unit testing, is to always test both the positive and negative scenarios. So since we currently have two test cases:

1) Test isValidUsernameAndPassword method with a valid username/password
2) Test isValidUsername method with an invalid username

We should also automatically test the opposite of these two outcomes, so we should have tests for:

3) Test isValidUsernameAndPassword method with an invalid username/password
4) Test isValidUsername method with a valid username

Make sense? I won't include these tests in here, as they'll just take up extra space, and plus it may be more fun for YOU to write those tests yourself :)

Now, you might be thinking that this code is pretty solid right? We've written a few tests and everything is green and good. But what if we introduce this test:

@Test
public void testAnotherValidUsernameAndPassword ()
{
  boolean result = sut.isValidUsernameAndPassword("User2", "howtoprogramwithjava");
  assertTrue(result);
}

This test (as is stated in the name of the test method), should be testing another valid username and password combination. But, this time the test FAILS! What the heck happened? Here's a screenshot of the failure.

Unit Testing

Well what the heck happened here?

Upon closer inspection, you may notice that I accidentally made the username "User2" with a capital "U" instead of a lowercase "u". But this is very interesting. How should our system react when you give uppercase vs. lowercase letters in the username? This is a result that may not have been caught with regular testing, or if it was caught, it may have taken a while to reproduce if you had accidentally typed in an uppercase letter. This is the beauty of unit testing, it's easy to re-create a failing test!

So what do we do? If this was the real world, you'd likely have to re-read the specifications to see if they tell you what to do in this case, and if the requirements aren't detailed enough, then you'd likely talk to the business analyst who helped write that part of the requirements document. In this case, I'm the one who wrote the requirements, and I purposely made them a little vague around case sensitivity.

So, in this case I'll just say that the password needs to be case sensitive and the username should not be case sensitive.

Let's go back and change our application code with these new requirements:

public class Login
{
  public boolean isValidUsername(String username)
  {
    //... content removed for brevity

      // here we use equalsIgnoreCase() instead of just equals()
      if (username.equalsIgnoreCase(aUser.getUsername()))
      {
        return true;
      }
   //...
  }

  public boolean isValidUsernameAndPassword (String username, String password)
  {
    //... content removed for brevity

      // here we use equalsIgnoreCase() instead of just equals() on the username
      // but we leave the equals() comparison for the password!
      if (username.equalsIgnoreCase(aUser.getUsername()) && password.equals(aUser.getPassword()))
      {
        return true;
      }
    //...
  }

  // ...
}

So, now that we've identified a real bug and we've changed our application's code to fix the bug. Let's go back and re-run our tests!

If all goes well, you'll see a green bar and breath a sigh of relief.

Now, I'm sure there are a bunch of other test cases that you could think up. I know it's always useful to test with null values to ensure your code won't throw any unwanted exceptions. Why don't use try to write some tests that use null for the values of username and password? I think you'll see some interesting results :)

Summary

Automated testing (via JUnit or any other testing framework) is the wave of the future. All applications should be built on a solid foundation of unit tests to ensure that everything is still working the way it's expected to work as the product evolves. It's the difference between happy customers and angry customers. So it's absolutely worth the effort to create unit tests to avoid these headaches :)

Alright ladies and gentlemen, it's that time again, where I ask you to do me a solid and share my articles with your friends! Please please please click the social sharing buttons on the top left of your screen right now and let others know that there are awesome, free Java tutorials available that are easy to follow and in plain English! You wouldn't want to deprive your friends of that now would you?

Have a great day everyone, and best of luck in your learning!

Free Java Beginners Course

Start learning how to code today with our free beginners course. Learn more.