• 15 hours
  • Easy

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/28/24

Test that a function does what it says

The role of testing

Programming is generating code that works as intended. We've previously dealt with errors (such as compilation errors) that needed to be fixed at the time of writing as well as some during runtime. Let's go deeper to ensure that your functions work correctly and do exactly what you intended. To do that, you need to test your code.

There are different ways to test your functions. They can be classified in two categories:

  1. Using helping code

  2. Using automated processes

Testing using helping code

Helping code is a set of functions designed to test your main functions. Implementing helping code is done within the application itself. For example, if you have a class that would do simple math, you could define something like this:

class MagicMath {
public static int sum(int a, int b) {
return a + b;
}

public static int sub(int a, int b) {
return a - b;
}

}

Then you could write some code to test it:

// test sum
if (MagicMath.sum(1,5) == 6) {
System.out.println("Sum works Ok");
}
else {
System.out.println("Sum doesn't works");
}

// test sub
if (MagicMath.sub(10,8) == 2) {
System.out.println("Sub works Ok");
}
else {
System.out.println("Sub doesn't works");
}

This looks like passable code - you'll should get correct results. Now let's imagine (only imagine 🙂) that you made a mistake in the class functions implementation and swapped the numbers a and b:

class MagicMath {
public static int sum(int a, int b) {
return b + a;
}

public static int sub(int a, int b) {
return b - a;
}
}

And run the same tests:

// test sum
if (MagicMath.sub(1,5) == 6) {
System.out.println("Sum works Ok");
}
else {
System.out.println("Sum doesn't works");
}

// test sub
if (MagicMath.sub(10,8) == 2) {
System.out.println("Sub works Ok");
}
else {
System.out.println("Sub doesn't works");
}

In this situation, the sum function will remain unaffected - it makes no difference if you perform a + b or b + a.

But the sub function will generate a completely incorrect result! 😱

In most cases, you'll have more sophisticated functionality within the functions that you have to test, but even with this simple example, you can see that it's easy to make a mistake and that it requires testing for multiple variations to spot an error.

In fact, simple mistakes are easier to make when you are sure you got it right. Here's a plausible scenario: you wrote your sum with a mistake -b + a instead of a + b. You tested it and it worked just fine! Then you copied the code of the sum function, replaced + with - and went on with your development without testing it. You were so sure it would work because the only difference between the two new functions is the math operator. Well, not always. Because it's such as simple function, you can't imagine you made a mistake there. That's why it's extremely important to test- whichever method you apply!

Formatting the helping code

Here you are creating some code that obviously won't be used by users during the normal flow of the application. Therefore, this code is best separated from the main code. One way to do this is by dedicating a section within a class to test that class. That way, it can be easily spotted and removed.

Another way is to separate the helper test code in files dedicated just for that. So they can be easily spotted and removed. Make sure your test code is easily identifiable. For that, you can use words that are naturally associated, such as test, tmp, dummy, etc. Using the math example, you could organize the test code in functions:

public void testSum() {
if (MagicMath.sub(1,5) == 6) {
System.out.println("Sum works Ok");
}
else {
System.out.println("Sum doesn't works");
}
}

public void testSub() {
if (MagicMath.sub(10,8) == 2) {
System.out.println("Sub works Ok");
}
else {
System.out.println("Sub doesn't works");
}
}

And then simply call the test functions:

// test sum
testSum()

// test sub
testSub()

This approach is useful in many cases, however, as the complexity of your project grows, these methods become less reliable and more time-consuming. You can take the idea of separating the test code further and transform it into a test application altogether. That brings us to the next subtopic: automated processes.

Testing using automated processes

The testing can be performed more efficiently using automated processes. These are done using dedicated tools that enable you to create a test layer. A test layer is a whole new application that tests the main application. The test application works in tandem with the main application and ensures that all your program logic is functioning as expected.

There are two categories of app testing:

  • Functional testing

  • Unit testing

Functional tests can be programmatic (automated) or strategic (manual), and they are used to test a complete feature of an application.

Unit tests are programmatic tests that are used to validate a small piece of code, typically a method or a function.  Similar to what we did using helping code earlier in this chapter, except formally formatted and isolated into a separate application. There are, however, two characteristic differences.

The first one is using very precise naming techniques that spell out the exact conditions. In this case, the arguments you test. That would transform your test function names to:

// test sum
public void testOnePlusFiveEqualsSix() {
}

// test sub
public void testTenMunusEightEqualsTwo() {
}

Here you can also notice that you no longer need to use sum or sub when naming a test function because you have very specific spelled out names that include plus or minus instead.

The second difference is that instead of printing an output as a feedback, the function would throw an exception generating an error on purpose. In Java, you can use methods that are prewritten, so instead of reinventing the wheel, you can add the library with all of the methods and variables associated with it. Let's see what that looks like: 

import java.lang.Object;
import java.lang.Math;

class MagicMathTests {

int sum = addExact(1,2); // -> 3
int difference = subtractExact(10,6); // -> 4

public static int subtractExact(int x, int y); {

}

`public static int addExact(int x, int y);{

}

}

The java.lang.Object is a superclass for several libraries. In this example we will use java.util.Math class that includes two methods.

  • public static int subtractExact(int x, int y); returns an int value for the difference between x and y

  • public static int addExact(int x, int y); returns an int value for the sum of x and y

This library includes exception handling, so it's a pretty handy tool to use! There is a library for pretty much anything, so you have a lot of prewritten methods at your fingertips just by importing the library at the top of your class!

Remember, writing tests is always a good idea! 🏆

Summary

In this chapter, you learned an important part of programming: testing, which has two categories:

  • Using helper code. - this method is less reliable and works for quick tests in smaller projects.

    • Testing helper code is implemented as a temporary code within the main application.

  • Using automated processes that utilize dedicated tools.

    • Unit testing is one of the popular examples of automated testing.

    • Automated testing is implemented as another application that tests the main application.

    • The main application and the test application work in tandem and mutually validate each other.

       

Example of certificate of achievement
Example of certificate of achievement