Comprehensive test management solution for complex challenges.
Try-catch and assertions in Java
You have written your code and are firmly convinced that it is correct. After all, you have gone through all possible scenarios in detail in your head and the compiler is also satisfied with your code for the time being. An unpleasant surprise is often not long in coming when the program crashes after a few moments due to an exception or an error. A familiar situation. Sobered, you set about debugging. But what is the difference between an exception and an error? And when can you use them to your advantage?
Exceptions and Errors
In Java, Exceptions and Errors are direct subclasses of the Throwable class. They are instantiated by the JVM as soon as an abnormal program flow is registered. However, Exceptions and Errors can also be explicitly preprogrammed.
public class ExceptionExperiments {
public static void main(String[] args) throws Exception {
throw new Exception();
}
}
*Fig. 1 This program crashes due to an explicitly called exception.
Errors describe serious events that prevent further execution of the program. These include, for example, the OutOfMemoryError or the StackOverflowError. Exceptions are unexpected situations, with which the program is confronted. If not handled appropriately, exceptions also lead to a program crash. Frequently encountered exceptions are e.g. the FileNotFoundException or the IndexOutOfBoundException.
Handling of exceptions
To protect the program against potentially occurring exceptions, these must be handled. The handling informs the program at which places in the code an exception can occur and how to proceed in such cases. Handling can even be mandatory if so-called checked exceptions occur. For example, if a program is instructed to open and read a file, it can be assumed that this file may not exist, resulting in a FileNotFoundException. It is mandatory that this case be handled, which is why the FileNotFoundException is a checked exception. Before a checked exception is not handled, the code cannot be compiled (and thus cannot be executed). The ArithmeticException always occurs when dividing by zero. It can, but does not have to be handled, so it is an example of the unchecked exceptions. Whether an unchecked exception should be handled often depends on the situation.
public class ExceptionExperiments {
public static void main(String[] args) {
uncheckedExceptionExample();
checkedExceptionExample();
}
public static void uncheckedExceptionExample() {
System.out.println(3/0);
}
public static void checkedExceptionExample() {
File file = new File("HelloWorld.txt");
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
System.out.println(myReader.nextLine());
}
myReader.close();
}
}
Fig. 2 The uncheckedExceptionExample method will result in an ArithmeticException, because division by zero is forbidden. However, the code cannot be compiled this way. The method checkedExceptionExample could cause a FileNotFoundException and must be handled!.
Handling exceptions by try-catch(-finally)
Exceptions can be effectively handled by the try-catch construct. Here, the try clause contains the code that can cause an exception. The catch clause specifies the exceptions to be caught, as well as other statements that the program should follow after catching the exception. Optionally, a finally clause can be added, which is always executed after the try or after the catch clause. This can be especially useful if used resources are to be finally cleaned up, i.e., closed, saved, and released.
public class ExceptionExperiments {
public static void main(String[] args) {
checkedExceptionExample();
}
public static void checkedExceptionExample() {
try {
File file = new File("HelloWorld.txt");
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
System.out.println(myReader.nextLine());
}
myReader.close();
}
catch (FileNotFoundException fnfe) {
System.out.println("File does not exist.");
}
finally {
//Perhaps necessary cleanup
}
}
}
*Fig. 3 The checked exception FileNotFoundException is handled by the try-catch-finally construct. If the exception occurs, it is caught and the message "File does not exist." is printed to the console.
Delegating exceptions
As an alternative to a try-catch construct, exceptions can be passed on. The requirement is that the exceptions are marked together with the keyword throws in the method header. The handling of exceptions is then the responsibility of the caller method.
public class ExceptionExperiments {
public static void main(String[] args) {
try {
checkedExceptionExample();
}
catch (FileNotFoundException fnfe) {
System.out.println("File does not exist.");
}
finally {
//Perhaps necessary cleanup
}
}
public static void checkedExceptionExample() throws FileNotFoundException {
File file = new File("HelloWorld.txt");
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
System.out.println(myReader.nextLine());
}
myReader.close();
}
}
*Fig. 4 The exception FileNotFoundException was delegated from the method checkedExceptionExample to the caller method main. This catches the exception with a try-catch-finally construct.
Assertions
Assertions can be used to quickly test assumptions about program logic. The expected behavior is reformulated into a Boolean condition, which is evaluated by an assertion. In case of discrepancies an AssertionError is generated and the program is aborted. For this reason Assertions are suitable outstanding for the uncovering of Bugs and are an indispensable component of software tests. The error AssertionError belongs to the class Error. Analogous to exceptions, errors can be caught in a try-catch construct to prevent a program crash. However, errors usually indicate serious circumstances under which further execution of the program should no longer be possible. Therefore, any attempts to handle the AssertionError as well as other errors are discouraged. It is considered good practice to implement only one assertion per test. The test will be aborted as soon as this assertion fails. If the assertion is followed by further code, this code will not be executed. This type of assertion is therefore also called a hard assertion.
public class AssertionExperiments {
int a = 2, b = 30;
@Test
public void testMultipleHardAssertions() {
Assert.assertEquals(a + b, 31);
Assert.assertEquals(a * b, 60);
}
*Fig. 5 The test fails while evaluating the first assertion and is aborted. The second assertion is not evaluated.
If several independent aspects are to be tested, this can be accomplished with the help of so-called soft assertions in a single test. Soft assertions are part of the testing framework TestNG, which is built on top of the standard JUnit. When soft assertions are used, the test is not considered finally passed or failed until all soft assertions in the test have been evaluated.
Summary
With the help of try-catch constructs programs can be secured against exceptions and thus be made more robust. They are used therefore equally in the development and quality assurance of software. The use of try catch constructs is optional in many cases. At the latest however, if a checked Exception threatens to occur, the treatment is necessary by a try catch construct. Also, when protecting the program against unchecked exceptions, try-catch constructs offer a much better alternative to impractical and poorly readable if-clauses. Since assertions provide a means of checking program behavior, they are used predominantly in software testing. They generate an AssertionError when assumptions about program behavior are not confirmed. Assertions replace impractical and poorly readable if-clauses with explicitly pre-programmed Errors. Soft assertions allow testing multiple independent aspects in a single test, eliminating the need for controversial detours via hard assertions in try-catch constructs.
try-catch | hard-assertion | soft-assertion |
---|---|---|
area of application | software development & software testing | software testing |
Purpose | Handles exceptions, prevents program termination and increases robustness. | Generates an error if tested assumption about program logic is not confirmed. |
Replaces | if-clauses to protect the program against unchecked exceptions | if-clauses with explicit errors in software tests |