410 likes | 539 Views
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”
E N D
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; // adds a number to the array, // resizes if necessary public void add(intnum) { if (this.size == this.array.length) this.resize(); this.array[size++] = num; } private void resize() { int[] temp = new int[this.array.length * 2]; for (inti = 0; i < this.array.length; i++) temp[i] = this.array[i]; this.array = temp; } } 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 Add at least 5 numbers to make sure the array resizes
Testing Example public class TestExample { private int[] array = new int[4]; private int size = 0; // adds a number to the array, // resizes if necessary public void add(intnum) { if (this.size == this.array.length) this.resize(); this.array[size++] = num; } private void resize() { int[] temp = new int[this.array.length * 2]; for (inti = 0; i < this.array.length; i++) temp[i] = this.array[i]; this.array = temp; } } 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 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
Boundary Cases • You should always test the boundary cases, the ones that are on the border • Test -2, -1, 4, 5, 6 – numbers that are edge cases • Always test valid and invalid numbers if (-2 < a && a <= 5) …
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 • 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 Exception e1 = new Exception(); Exception e2 = new NullPointerException(); Exception e3 = new InputMismatchException();
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 Somewhere in the constructor for FileReader defined by Java, they wrote “throw new IOException()” try { Scanner scanner = new Scanner(new FileReader(“file.txt”)); // do something here } catch (IOException e) { e.printStackTrace(); } You can think of the code saying: “Do this code. If this exception is caused, stop executing, and execute this code instead.” 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. 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!
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 { System.out.println(mySqrt(-4)); } public static void main(String[] args) { 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); char choice; do { choice = kb.next().charAt(0); switch (choice) { choice ‘a’: Scanner scanner = new Scanner(new FileReader(kb.nextLine())); // do something with the scanner break; } } while (choice != ‘q’); } If the file does not exist, this is going to cause a FileNotFoundException Program would crash!
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); switch (choice) { choice ‘a’: 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’); } The scanner code stops execution, it displays “File could not be found” and continues. It does NOT crash
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 public void readInputFile(String filename) { try { Scanner scanner = new Scanner( new FileReader(filename)); // read in and do something with the data } catch (IOException e) { // deal with the exception here } } public void readInputFile(String filename) throws IOException { Scanner scanner = new Scanner( new FileReader(filename)); // read in and do something with the data } createTable createDatabase main Call Stack We’re saying watch out, we might run into this problem and I don’t want to deal with it. We’re saying I know this issue might 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 public void createTable() throws IOException { String filename = “file.txt”; readInputFile(filename); } createTable createDatabase main Since readInputFile could throw an IOException, we must handle it or say that createTable could also throw the exception Call Stack 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 We don’t want the program to crash, so we’re going to handle the Exception in createDatabase readInputFile throws IOException createTable throws IOException Now if the IOException is thrown in readInputFile, the code stops executing in readInputFile and createTable, and it goes to the catch block here public void createDatabase() { try { createTable(); } catch (IOException e) { System.out.println(“Error”); } } createDatabase main Call Stack 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 • We can now say “throw new EvenNumberRequiredException()” public class EvenNumberRequiredException extends Exception { public EvenNumberRequiredException() { super(“An even number is required”); } }
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: try { crazyMethod(); } catch (IOException e) { // do something } catch (FileNotFoundException e) { // do something } … Exception Is a IOException Is a FileNotFoundException A FileNotFoundExcetpion is an IOException, so which catch block get executed? 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: try { crazyMethod(); } catch (FileNotFoundException e) { // do something } catch (IOException e) { // do something } … 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.
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.