Testing and Error Handling Intro to Java Testing • We test to try and make sure our programs work correctly and have no bugs • If we have access to the code, we look at the structure of the code and try to make sure every case is tested. This is called “white box testing” • Otherwise, we test the code seeing if it does its job and if what it does matches the Javadocs. This is called “black box testing” Testing Example public class TestExample { private int[] array = new int[4]; private int size = 0; White box testing: we can see the code (we can only directly call the add() method). What would you test? Add a number, make sure it’s there // adds a number to the array, // resizes if necessary public void add(int num) { if (this.size == this.array.length) this.resize(); this.array[size++] = num; } Add at least 5 numbers to make sure the array resizes private void resize() { int[] temp = new int[this.array.length * 2]; for (int i = 0; i < this.array.length; i++) temp[i] = this.array[i]; this.array = temp; } } Testing Example public class TestExample { private int[] array = new int[4]; private int size = 0; Black box testing. We can only see the add() method exists and the Javadocs What would you test? Add a number, make sure it’s there // adds a number to the array, // resizes if necessary public void add(int num) { if (this.size == this.array.length) this.resize(); this.array[size++] = num; } Add a bunch of numbers and make sure they are all there We don’t know what value the array first resizes, so we have to add a bunch to hope we hit the case when it does resize private void resize() { int[] temp = new int[this.array.length * 2]; for (int i = 0; i < this.array.length; i++) temp[i] = this.array[i]; this.array = temp; } } Boundary Cases • You should always test the boundary cases, the ones that are on the border if (-2 < a && a <= 5) … • Test -2, -1, 4, 5, 6 – numbers that are edge cases • Always test valid and invalid numbers More Testing • Regression testing: if you update/add code, make sure what worked before still works • Unit testing: individually test each method with its own mini driver program. Typically used with white box testing • Good practice to write code and unit tests at the same time Testing Data • Where does the data come from? • You can make up values that you think the problem should have trouble with (invalid cases) • You can write programs to randomly generate a lot of data. This is helpful for blackbox testing. • Example: you write a program that adds 1,000,000 random Pokemon to a trainer and make sure that it doesn’t crash Error Handling • How do we handle errors in programming? • What is an error? • NullPointerException, FileNotFoundException, IllegalArgumentException, etc. • Display error message with System.err.println() • Quit the program with System.exit(1) • Both of these are really ugly – can we do better? Exception Handling • What is an Exception? • An Object which contains information about the error that just happened Exception e1 = new Exception(); Exception e2 = new NullPointerException(); Exception e3 = new InputMismatchException(); • You’ve seen a lot of these. You can even subclass them to create your own! • How does this help us? First, let’s talk about the Call Stack Call Stack • The Call Stack in Java shows you the path through the program the computer took to reach where it is • Keep a stack (like a stack of plates) of all of the methods you call. • Once you enter a new method, you put that on top of the stack. • If you exit a method, remove it from the stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodA main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodA main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodB methodA main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodB methodA main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodA main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodA main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } methodC main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } main Call Stack Stack Example public static void main(String[] args) { System.out.println(“starting”); methodA(); methodC(); } private static void methodA() { System.out.println(“Starting A”); methodB(); System.out.println(“done A”); } private static void methodB() { System.out.println(“Starting B”); System.out.println(“Done B); } private static void methodC() { System.out.println(“Starting and ending C”); } Call Stack Call Stack • How do you read this? Bottom up • • • • The main method called methodA on line 12 methodA called methodB on line 17 methodB called methodC on line 22 methodC had an error on line 27 Back to Exceptions • So how do you actually create an exception? • Since it’s an Object, you use the new keyword • The keyword for exceptions is throw public double mySqrt(double num) { if (num < 0) throw new IllegalArgumentException(“The value of num must be non-negative”); return Math.sqrt(num); If this happens, the method stops executing right here } and returns to the method that called it This is the Java way to say “Something went wrong. I don’t know how to deal with it, make someone else fix it” Try Blocks • Now what? We handle exceptions with what’s called a try block You can think of the code saying: “Do this code. If this exception is caused, stop executing, and execute this code instead.” try { Somewhere in the constructor for FileReader defined by Java, they wrote “throw new IOException()” Scanner scanner = new Scanner(new FileReader(“file.txt”)); // do something here } catch (IOException e) { e.printStackTrace(); } Inside the try, you put the code that might “throw” (or cause) an exception Now you don’t have to write “throws IOException” at the top! We are declaring an IOException object called e here. Java will assign the value of e for us. Since e is as object, we can call methods on it. Throwing Exceptions • If a method could potentially throw an Exception, you have two choices: • Handle it with a try-catch block • Declare that the method which calls it could also throw that same exception public static void main(String[] args) throws IllegalArgumentException public static void main(String[] args) { { System.out.println(mySqrt(-4)); try { } System.out.println(mySqrt(-4)); } catch (IllegalArgumentException e) { System.out.println(“Oops!”); Potentially throws an IllegalArgumentException } } Here, your program ends gracefully Here, your program crashes! Example Before public static void main(String[] args) throws IOException { Scanner kb = new Scanner(System.in); If the file does not exist, char choice; this is going to cause a do { FileNotFoundException choice = kb.next().charAt(0); switch (choice) { Program would crash! choice ‘a’: Scanner scanner = new Scanner(new FileReader(kb.nextLine())); // do something with the scanner break; } } while (choice != ‘q’); } Example After If the file does not exist, a FileNotFoundException is thrown, but it is “caught” with our catch block public static void main(String[] args) { Scanner kb = new Scanner(System.in); char choice; do { choice = kb.next().charAt(0); The scanner code stops execution, it displays switch (choice) { “File could not be found” and continues. It choice ‘a’: does NOT crash try { Scanner scanner = new Scanner(new FileReader(kb.nextLine())); // do something with the scanner } catch (FileNotFoundException e) { System.out.println(“File could not be found”); } break; } } while (choice != ‘q’); } Exceptions and the Call Stack • What actually happens when we throw an Exception? readInputFile tries to read a file. If it handles the exception (it uses a try-catch block), then we don’t have to do anything else. If it does not handle the exception, it must declare that it “throws IOException” readInputFile createTable createDatabase main Call Stack public void readInputFile(String filename) public void readInputFile(String filename) { throws IOException try { { Scanner scanner = new Scanner( Scanner scanner = new Scanner( new FileReader(filename)); new FileReader(filename)); // read in and do something with the data } catch (IOException e) { // read in and do something with the data // deal with the exception here } } We’re saying watch out, we might run } into this problem and I don’t want to deal We’re saying I know this issue might with it. happen and I’m going to take care of it if it does. This won’t compile unless you do one of these two options. Exceptions and the Call Stack • Let’s say readInputFile didn’t handle the exception • That means createTable must either handle it with a try-catch block OR it can make someone else handle it readInputFile throws IOException createTable createDatabase main Call Stack public void createTable() throws IOException { String filename = “file.txt”; readInputFile(filename); } Since readInputFile could throw an IOException, we must handle it or say that createTable could also throw the exception Let’s just say that createTable also does not handle it Exceptions and the Call Stack • The error keeps getting thrown up the Call Stack until someone deals with it • If we keep throwing it and it’s not handled in main, then the program crashes readInputFile throws IOException We don’t want the program to crash, so we’re going to handle the Exception in createDatabase createTable throws IOException createDatabase main Call Stack public void createDatabase() { try { createTable(); } catch (IOException e) { System.out.println(“Error”); } } Now if the IOException is thrown in readInputFile, the code stops executing in readInputFile and createTable, and it goes to the catch block here Not being able to find the file in readInputFile will now be unable to make our program crash. Creating Our Own Exceptions • What if you need to have an error that isn’t really the same as any of the built in Java ones? • We can create our own! Just extend the Exception Object public class EvenNumberRequiredException extends Exception { public EvenNumberRequiredException() { super(“An even number is required”); } } • We can now say “throw new EvenNumberRequiredException()” What about Multiple Exceptions • Some methods could potentially throw more than one Exception • So create more than 1 catch block • You can only go into 1 catch block, then you keep executing code try { crazyMethod(); } catch (IOException e) { // do something } catch (NullPointerExecption e) { // do something } … What about Multiple Exceptions • What happens with inheritance here? • If you look at the Exception hierarchy: Exception Is a IOException Is a FileNotFoundException A FileNotFoundExcetpion is an IOException, so which catch block get executed? try { crazyMethod(); } catch (IOException e) { // do something } catch (FileNotFoundException e) { // do something } … Always the first one! Here, the FileNotFound catch block will NEVER be executed What about Multiple Exceptions • What happens with inheritance here? • If you look at the Exception hierarchy: Exception Is a IOException Is a FileNotFoundException Now if a FileNotFoundException is thrown, it will execute the FileNotFound catch block and skip the IOException block. try { crazyMethod(); } catch (FileNotFoundException e) { // do something } catch (IOException e) { // do something } … What about Multiple Exceptions • If you want to catch every possible Exception with 1 catch block, catch Exception try { badMethod(); } catch (Exception e) { // do something } Checked versus Unchecked • Checked exceptions must be handled or declared that they are thrown • IOException, FileNotFoundException, etc. • Unchecked exceptions can be handled, but you don’t need to declare that they are thrown • NullPointerException, ArrayIndexOutOfBoundsException, etc. • Unchecked exceptions all extend RuntimeException Finally Block • A finally block will be executed after the try no matter how the try block is exited PrintWriter writer = null; String filename = kb.nextLine(); try { writer = new PrintWriter(new FileWriter(filename)); // write something to the file } catch (IOException e) { System.err.println(“Problem writing to “ + filename); } finally { if (writer != null) writer.close(); } Summary • Exceptions are a way of error handling in Java • If a method could cause an exception, it must either handle it with a try-catch block or it has to declare that it throws that exception • If it declares it will throw the exception, every method which calls it must handle it or throw it. This repeats (recursively!) until main. If main throws it, the program crashes.