Java Assertions: Testing Assumptions

JavaJavaBeginner
Practice Now

Introduction

In this lab, you will learn about assertions in Java. You will learn how to create assertions and when to use them. Assertions are used to test assumptions regarding our code and are mostly utilized during the testing phase of the software.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("`Java`")) -.-> java/ProgrammingTechniquesGroup(["`Programming Techniques`"]) java(("`Java`")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["`Object-Oriented and Advanced Concepts`"]) java(("`Java`")) -.-> java/FileandIOManagementGroup(["`File and I/O Management`"]) java(("`Java`")) -.-> java/BasicSyntaxGroup(["`Basic Syntax`"]) java(("`Java`")) -.-> java/DataStructuresGroup(["`Data Structures`"]) java(("`Java`")) -.-> java/StringManipulationGroup(["`String Manipulation`"]) java(("`Java`")) -.-> java/SystemandDataProcessingGroup(["`System and Data Processing`"]) java/ProgrammingTechniquesGroup -.-> java/scope("`Scope`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("`Classes/Objects`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/class_methods("`Class Methods`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/encapsulation("`Encapsulation`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/modifiers("`Modifiers`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/oop("`OOP`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/packages_api("`Packages / API`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/user_input("`User Input`") java/FileandIOManagementGroup -.-> java/files("`Files`") java/BasicSyntaxGroup -.-> java/identifier("`Identifier`") java/DataStructuresGroup -.-> java/arrays("`Arrays`") java/BasicSyntaxGroup -.-> java/booleans("`Booleans`") java/BasicSyntaxGroup -.-> java/break_continue("`Break/Continue`") java/BasicSyntaxGroup -.-> java/comments("`Comments`") java/BasicSyntaxGroup -.-> java/data_types("`Data Types`") java/BasicSyntaxGroup -.-> java/if_else("`If...Else`") java/BasicSyntaxGroup -.-> java/math("`Math`") java/BasicSyntaxGroup -.-> java/operators("`Operators`") java/BasicSyntaxGroup -.-> java/output("`Output`") java/StringManipulationGroup -.-> java/strings("`Strings`") java/BasicSyntaxGroup -.-> java/switch("`Switch`") java/BasicSyntaxGroup -.-> java/variables("`Variables`") java/BasicSyntaxGroup -.-> java/while_loop("`While Loop`") java/SystemandDataProcessingGroup -.-> java/math_methods("`Math Methods`") java/SystemandDataProcessingGroup -.-> java/string_methods("`String Methods`") java/SystemandDataProcessingGroup -.-> java/system_methods("`System Methods`") subgraph Lab Skills java/scope -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/classes_objects -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/class_methods -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/encapsulation -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/modifiers -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/oop -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/packages_api -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/user_input -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/files -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/identifier -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/arrays -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/booleans -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/break_continue -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/comments -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/data_types -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/if_else -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/math -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/operators -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/output -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/strings -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/switch -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/variables -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/while_loop -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/math_methods -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/string_methods -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} java/system_methods -.-> lab-117470{{"`Java Assertions: Testing Assumptions`"}} end

Creating a Simple Assertion

Let's create a simple assertion that tests if a number is even. We will assume that the number is even and write an assertion using an even number. We will then alter the number and run the code. We should get an AssertionError.

public class AssertionsLab {
    public static void main(String[] args) {
        int number = 4;
        assert (number % 2) == 0;
        System.out.println("Number is even");

        // change the value of number variable to odd
        number = 3;
        assert (number % 2) == 0;
        System.out.println("Number is even");
    }
}

Disabling Assertions

By default, assertions are disabled in Java. We can enable assertions using command-line options.

Let's use the -ea option to enable assertions and run the code.

java -ea AssertionsLab

The code should print "Number is even" followed by an AssertionError.

Let's now disable assertions using the -da option and run the code again.

java -da AssertionsLab

The code should print "Number is even" twice without throwing any exceptions.

Using Assertions to Test Inputs

Assertions can also be used to test inputs to a method. Let's create a divide method that takes two parameters and returns the quotient after dividing the first parameter by the second parameter. We will assume that the second parameter is non-zero and create an assertion to check it.

public class AssertionsLab {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;
        int result = divide(a, b);
        System.out.println("Result: " + result);

        // change the value of b to zero
        b = 0;
        result = divide(a, b);
        System.out.println("Result: " + result);
    }

    private static int divide(int a, int b) {
        assert b != 0 : "Cannot divide by zero";
        return a / b;
    }
}

Using Assertions for Debugging and Testing

We can use assertions for debugging and testing. Assume that we have a program that reads a file and returns the first non-zero number from it. To test this program, let's create an input file with three numbers.

echo "0\n3\n0" > input.txt

Let's write our program that reads the file and returns the first non-zero number from it. We will then create an assertion to test if the program returns the correct output.

import java.io.File;
import java.io.FileNotFoundException;
+ import java.util.Scanner;

public class AssertionsLab {
    public static void main(String[] args) throws FileNotFoundException {
        Scanner scanner = new Scanner(new File("input.txt"));
        int number = 0;
        while (scanner.hasNext()) {
            number = scanner.nextInt();
            if (number != 0) {
                break;
            }
        }
        assert number == 3 : "Incorrect number read from file";
        System.out.println("First non-zero number: " + number);
    }
}

When Not to Use Assertions

Assertions are not suitable for validating arguments of public methods. These assertions may be disabled during runtime which can lead to unexpected behavior.

public class AssertionsLab {
    public static void main(String[] args) {
        int negativeNumber = -1;
        printPositiveNumber(negativeNumber);
    }

    public static void printPositiveNumber(int number) {
        assert (number > 0) : "Not a valid positive number";
        System.out.println("Positive number: " + number);
    }
}

Make Assertions Informative

We can add informational messages to assertions to make them more informative and provide better feedback in case of a failure. We will now modify the assertion in the previous code to include an informative message.

public class AssertionsLab {
    public static void main(String[] args) {
        int number = -1;
        assert (number > 0) : "Negative number found";
        System.out.println("Number is positive: " + number);
    }
}

The message "Negative number found" will be displayed when the code is executed.

Preventing Assertions from Breaking Code

The purpose of assertions is generally to find bugs when testing software. However, if the assertions themselves break code, then it becomes problematic. For example, a transient network error or a timing issue on a system can cause an assertion to fail. Additionally, if we use assertions to validate inputs on public methods, we run the risk of leaving our system unprotected against invalid and malicious inputs.

One way to circumvent the negative impact of assertions is to use them judiciously. Use assertions only for things that should never happen in a well-designed system.

public class AssertionsLab {
    public static void main(String[] args) {
        double result = squareRoot(4);
        System.out.println("Square root: " + result);

        double negativeNumber = -4;
        result = squareRoot(negativeNumber);
        System.out.println("Square root: " + result);
    }

    public static double squareRoot(double number) {
        assert number >= 0 : "Number should be non-negative";
        return Math.sqrt(number);
    }
}

Avoiding Side Effects

It is important to avoid side effects while using assertions. We should avoid changing variables within assertions. Instead, the expressions should be used to detect problematic situations and provide more diagnostic information.

public class AssertionsLab {
    public static void main(String[] args) {
        int firstNumber = 1;
        int secondNumber = 2;
        assert (firstNumber = secondNumber) == 0 : "Values are not equal";
        System.out.println("Values are equal");
    }
}

Using Assertions to Check Switch Statements

We can use assertions to check if all possible cases of a switch statement have been covered. Let's create a switch statement that returns the name of a day based on its number. We will assume that the number is within the range of 0 to 6 and create an assertion to check it.

public class AssertionsLab {
    public static void main(String[] args) {
        int dayNumber = 2;
        String day = getDayName(dayNumber);
        System.out.println("Day: " + day);

        // set dayNumber to an invalid number
        dayNumber = 10;
        day = getDayName(dayNumber);
        System.out.println("Day: " + day);
    }

    public static String getDayName(int dayNumber) {
        String day;
        switch (dayNumber) {
            case 0:
                day = "Sunday";
                break;
            case 1:
                day = "Monday";
                break;
            case 2:
                day = "Tuesday";
                break;
            case 3:
                day = "Wednesday";
                break;
            case 4:
                day = "Thursday";
                break;
            case 5:
                day = "Friday";
                break;
            case 6:
                day = "Saturday";
                break;
            default:
                assert false : "Invalid day number";
        }
        return day;
    }
}

Summary

Congratulations! You have completed the Java Assertions lab. You can practice more labs in LabEx to improve your skills.

Other Java Tutorials you may like