Testing and Error Handling

advertisement
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.
Download