Week11 - Exceptions and Unit Testing
Week11 - Exceptions and Unit Testing
Becir Isakovic
Lectures Schedule #1
Week 1 - User input, Printing on screen & Conditional statements
Week 3 - Methods, ArrayList data structure, Streams and connecting to the database - Quiz 1
Week 7 - Lambda functions, Records, Optionals , final keyword, & Preparation for the Midterm Exam -
Quiz 3
Lectures Schedule #2
Week 8 & 9 - MIDTERM
Week 10 - Regular expressions, File Manipulation, Iterators, Reflection & Custom Annotations
- If we try to to read from a file we will be forced by the compile to handle the
exception that could be returned when reading from file
- We have to handle this exception in order to get rid of the compile time error
- Checked exceptions are those that Java makes you handle at the compile time
(before the program even compiles)
Checked exception example
- Let us define a function that will accept a String parameter and print its length
and let us call this function with some parameter
public static void printLength(String myString){
System.out.println(myString.length());
}
public static void main(String[] args) {
printLength(null);
}
Unchecked exception example cont.
- We have called this function with a null value and if we run it it will return us the
NullPointerException
- But we do not have to handle this exception or add the throws declaration to the
method signature as the NullPointerException is the unchecked exception
- The NullPointerException is the subclass of the RuntimeException class and all
subclasses of the RuntimeException class and that class itself are of the
unchecked exception type
Exception cont.
- The parseInt method expect the parameter of type string that is actually an
number that Java can parse
- However we are sending the text instead of string number and this will cause an
NumberFormatException
Exception cont.
- First and foremost, it is extremely important to develop the habit of reading the
exceptions and figuring out what went wrong in your program
- The debugging is also one of the ways to dive deeper into the program, stop the
program execution and figure out what went wrong with the program
- This is a MUST skill for every software developer
- Even if you the best coder in the world, without being able to interpret the
response returned by a compiler or a Java program, your skills are useless
Exception handling
- The mechanism we are using in order to handle the exceptions is the try-catch
block
- We will put the code that might throw the exception in the try block, and if some
exception occurs, we will handle the exception in the catch block
- If you can recall, from our previous example, once the Java was not able to parse
the string to an integer, the exception was thrown, causing the whole to abort the
execution
- Now, by using the try-catch block, the program will not abort as the exception
was handled
Exception handling cont.
- Example:
try {
int numberParsed = Integer.parseInt("myint");
} catch (NumberFormatException exception){
System.out.println("Unable to cast");
}
- If we run the program it will print to the console: "Unable to cast", but it will not
cause the whole program to crash
- Of course, if the exception is not thrown, the catch block will never execute
Exception handling cont.
- It is important to note that the parameter in the catch block (type of the exception
we are trying to get) uses something called Exception Hierarchy
- This means that your catch block will only catch the exceptions that are of that
particular type or the exceptions that are the subclass of that exception class
- So often, in the catch block you will see that instead of specific exceptions, many
programmers are passing the exception of type Exception
- This will work as all of the exceptions are subclasses of the Exception class
- Let us rewrite our previous example
Exception handling cont.
- Example
try {
int numberParsed = Integer.parseInt("myint");
} catch (Exception exception){
System.out.println("Unable to cast");
}
- So if we go to the official Java documentation for the class
NumberFormatException you will see that this class is extending from the
Exception class
Exception handling cont.
- will not be executed as the program execution was restored at the catch block
Finally
- Finally will be executed no matter what you put in your code
- Even if, in the try or catch block, you return a value, the finally block will be
executed
try {
int numberParsed = getParsedInt("myInt");
System.out.println("This will not be written to the console");
return;
} catch (NumberFormatException nfe){
System.out.println("Unable to cast string to integer");
} catch (NullPointerException npe) {
System.out.println("The NullPointer has been thrown");
} finally {
System.out.println("I'm always executed");
}
Finally example
- So based on the previous knowledge what would be the output of the following
program
public static int finallyExample(){
try {
return 1;
return 1; } catch (Exception e){
return 2;
} finally {
return 3;
}
}
Custom exceptions
- What if we want to throw exception to the user from our code
- We will use the throw syntax with respective exception type
- Let us say we want to build a method that will accept the age: int parameter and
throw the exception if the age is negative
- Next thing is to add the new method for each unit you are testing
Unit testing cont.
Unit testing cont.
- As you might see, the code we are writing is under src/main/java/… while the tests
are in the folder /src/test/java/… and that is the right convention that you will
always be using
- In JUnit a unit test is just a method that is annotated with the @Test annotation
- We always unit that our unit test is a single-purpose test, meaning that it should
only be testing one single thing at a time
- Let us write the test to verify that our sample method add will return us 4 if we
pass add(2, 2) as an arguments
Unit testing example
public class SimpleCalculator {
public int add(int numA, int numB) {
return numA + numB;
}
}
@Test
public void twoPlusTwoEqualsFour() {
SimpleCalculator calculator = new SimpleCalculator();
assertEquals(4, calculator.add(2, 2));
}
Unit testing cont.
- The methods in the unit tests should not be public and always that should be of void return type
- The name of the methods can be whatever you want but in order to obey the convention rules and
should be descriptive and anybody who is reading the method name should be aware of what method
accepts and what is the result of the test
- One note is that the Java for the version 10 does have something called Local Variable Type
Inference that means that it can infer the type without explicitly defining a type by using the var
keyword
- This means that instead of
SimpleCalculator calculator = new SimpleCalculator();
- You can say
var calculator = new SimpleCalculator();
Unit testing cont.
- Each unit test will have one or more assert statements and we will be using them
- If you want to run your unit test, just press the run button next to your test as shown
in the image below
Unit testing fails
- If your unit test fails it will output what was expected assertion and what was
actually returned by the method call
- Let us say that, instead of the + sign in our add method, we put - sign
- The test will fail and we will have the explanation of what happened
JUnit Assertions
- There are many different assert methods that are being used, let us show some of
them
- assertArrayEquals - verifies that the expected and actual arrays are equal
- assertEquals and assertNotEquals- verifies expected input and actual output are equal or not
- assertTrue and assertFalse - verify the supplied conditions are true or false
- assertNull and assertNotNull - verify the supplied conditions are null or not
- assertSame and assertNotSame - verify the supplied inputs are same Objects
- assertAll - This assertion allows the creation of grouped assertions, where all the assertions are
executed and their failures are reported together by using lambdas
- assertThrows - assert if an executable throws the specified exception type.
JUnit Annotation
- The most common annotation that you have to use above every method is @Test
that marks a method as a test method. Without that annotation the method will not
be recognized as a test method.
- @BeforeAll / @AfterAll - methods that will be executed before/after other
methods
- @BeforeEach / @AfterEach - methods that will be executed before/after each
test in the class
- @Disabled - disable the test execution
- @Tag - string that allows grouping of the test or classes. We can select to
execute only those tests tag
JUnit Assertions Examples
@Test
public void whenAssertingArraysEquality_thenEqual() {
char[] expected = { 'J', 'u', 'p', 'i', 't', 'e', 'r' };
char[] actual = "Jupiter".toCharArray();
assertEquals(square, rectangle);
}
JUnit Assertions Examples
@Test
void whenAssertingEquality_thenNotEqual() {
float square = 2 * 2;
float rectangle = 2 * 3;
assertNotEquals(square, rectangle);
}
void whenAssertingConditions_thenVerified() {
assertTrue(5 > 4, "5 is greater the 4");
assertTrue(null == null, "null is equal to null");
}
JUnit Assertions Examples
@Test
public void givenBooleanSupplier_whenAssertingCondition_thenVerified() {
BooleanSupplier condition = () -> 5 > 6;
assertFalse(condition, "5 is not greater then 6");
}
@Test
void whenAssertingNotNull_thenTrue() {
Object dog = new Object();
assertNotNull(dog, () -> "The dog should not be null");
}
JUnit Assertions Examples
@Test
public void whenAssertingNull_thenTrue() {
Object cat = null;
assertNull(cat, () -> "The cat should be null");
}
@Test
void whenAssertingSameObject_thenSuccessful() {
String language = "Java";
Optional<String> optional = Optional.of(language);
assertSame(language, optional.get());
}
JUnit Assertions Examples
@Test
void whenAssertingSameObject_thenFail() {
String language = "Java";
assertNotSame(language, "Java is great");
}
@Test
void givenMultipleAssertion_whenAssertingAll_thenOK() {
Object obj = null;
assertAll(
"heading",
() -> assertEquals(4, 2 * 2, "4 is 2 times 2"),
() -> assertEquals("java", "JAVA".toLowerCase()),
() -> assertNull(obj, "obj is null")
);
Tests coverage
- Test coverage is a measure used in software testing to assess the extent to which a
set of test cases covers the functionality of a software application. It helps in
evaluating the thoroughness of testing by indicating the percentage of code or
functionalities that have been exercised during testing.
- In the IntelliJ there is also the view for the test coverage that shows the complete
coverage of the code by the tests
- High test coverage does not guarantee the absence of defects, but it can give an
indication of the areas that have been tested and those that may need additional
attention.
Tests coverage
- It is an important metric in software testing to help ensure the reliability and quality
of a software product.
- However, it's crucial to strike a balance, as achieving 100% coverage may not be
practical or necessary in all cases.
- The focus should be on testing critical and high-risk areas of the code.
Hands on experience
- Now we will create one small program that will read a list of songs from the file,
implement custom exceptions in it and create a unit tests for each method
- The code is already publicly available on our course github repository
https://github.com/ibecir/oop-2023
Resources
- https://github.com/ibecir/oop-2023
- https://www.youtube.com/watch?v=1XAfapkBQjk&ab_channel=CodingwithJohn
- https://www.youtube.com/watch?v=bCPClyGsVhc&ab_channel=CodingwithJohn
- https://www.youtube.com/watch?v=OIozDnGYqIU&ab_channel=CodingwithJohn
- https://www.youtube.com/watch?v=vZm0lHciFsQ&ab_channel=CodingwithJohn
- https://www.baeldung.com/junit-assertions#junit5-null
System.out.println("Thanks, bye!");