Unit tests: an introduction
Testing in Java: Beginner Level
Writing unit tests is an important part of the daily work of a professional developer. Unit tests force you to think about your code, and protect you and your colleagues from introducing annoying bugs during refactoring. A unit test is also a form of documentation that explain what is the expected behaviour of your code. For all these reasons a professional developer needs to be able to write good and solid unit tests.
When I start to write a test for a code me or someone else wrote, I usually try to have a simple checklist of 3 points in my mind to be sure I do not forget something. Now I will share this approach with you.
First I start with the most common scenario where everything suppose to work. I ask to myself:
What is the simplest scenario that my code will face?
Then I start to think about corner cases of this scenario.
And at the end I go for failing scenarios, asking questions like:
What happen if the argument I pass to my method is not valid?
It is simple: common, corner and failing scenarios. That´s all.
Following this checklist let´s try to write unit tests for this simple class:
public class ExampleClass {
public int integerDivision(int dividend, int divisor){
return dividend / divisor;
}
}
The code is pretty simple, we have one method that performs a division between integers and return the result.
Let´s start with the common scenario that my code need to face: the division of two integers.
Note that i follow the Arange Act Assert convention (http://wiki.c2.com/?ArrangeActAssert) when I write unit tests. I suggest you to do the same because make your code clearer and easy to be read.
My examples also use JUnit as framework. I have this set up phase:
private ExampleClass classUnderTest;
@Before
public void setUp(){
classUnderTest = new ExampleClass();
}
If you don´t have familiarity with JUnit, all you need to know is the method annotated with @Before runs before every tests. This will create a new fresh instance of the class under test every time.
Said that, the first test we write will be:
@Test
public void integerDivision_can_divide_two_integers() {
int expectedResult = 2;
int result = classUnderTest.integerDivision(4, 2);
assertEquals(expectedResult, result);
}
This covers the simplest and most common scenario our code will face everyday.
Our code performs a division of integers number, so let´s write a test that ensure that we will keep to perform this kind of division in the future even after one of our colleagues do a refactoring..
@Test
public void integerDivision_can_divide_two_integers_2() {
int expectedResult = 2;
int result = classUnderTest.integerDivision(5, 2);
assertEquals(expectedResult, result);
}
Everything looks fine for now, and it is time to start to think about corner cases for our code. For example:
What will it happen if my dividend is zero?
@Test
public void integerDivision_can_divide_zero() {
int expectedResult = 0;
int result = classUnderTest.integerDivision(0, 2);
assertEquals(expectedResult, result);
}
or
What will it happen if I divide negative integers?
@Test
public void integerDivision_can_divide_negative() {
int expectedResult = -1;
int result = classUnderTest.integerDivision(-2, 2);
assertEquals(expectedResult, result);
}
@Test
public void integerDivision_can_divide_negative_bis() {
int expectedResult = 1;
int result = classUnderTest.integerDivision(-2, -2);
assertEquals(expectedResult, result);
}
As you can see we started to look at corner cases that our code can face when is running. Let´s try another interesting case:
What will it happen if I try to divide by zero?
@Test
public void integerDivision_can_divide_by_zero() {
int expectedResult = 0; // not really used value
int result = classUnderTest.integerDivision(5, 0);
assertEquals(expectedResult, result);
}
Et voila´ we face an
ArithmeticException: / by zero
We found a serious problem for our code: of course it cannot perform a division by zero and it throws exception at runtime.
We don´t want this kind of behaviour. As developer we want to have the control of what our code does and to give meaningful information to our clients when something bad happens. So we can change the code of our class to better deal with this scenario:
public class ExampleClass {
public int integerDivision(int dividend, int divisor){
if(divisor == 0) {
throw new IllegalArgumentException("Division by zero is not allowed");
}
return dividend / divisor;
}
}
As you can see our class now performs a check on the argument that receives and throws a clear exception that inform the client that it cannot pass a divisor with a value of zero. We can also change the code of our test to better document this behaviour:
@Test(expected = IllegalArgumentException.class)
public void integerDivision_can_divide_by_zero_fixed() {
classUnderTest.integerDivision(5, 0);
}
This last test also covers the third point of our checklist: the failing scenarios.
Very often when you start to write unit test for your code this will drive you to think about new scenarios you never thought about.This is very good because it will force you to improve your code and make the life of your colleagues (and your) easier in the future.
This is all for this time, but if you have questions or feedback feel free to ask me!