In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use.[1]
Depending on the programming tecnique used, a unit could be:
- an entire module or an individual function (in procedural programming
- an interface or an individual method (in object oriented programming)
Unit Testing is made by test cases. Test cases usually have these features:
each test cases is indipendent from the others
- test cases are written by developers to ensure design behaves as requested/intented during the develoment phase
- each test case tests a module in isolation, so often method stubs, mock objects and test harness are used.
Advantages of unit test are:
- bug and problems comes out during the develoment phase (in TDD the test cases are developed before the code itself)
- regression testing and also refactoring the code is easier because once i have the test case, is easy to run them after a refactoring process
- integration testing is easier beacause once i test the module individually i have more chances the sum ofthe modules work correctly
- Unit tests code give a basic understanding of the unit’s interface (API) so this information can be used for documentation
Disadvantages of unit test are:
- cannot guarantee correct behavior for every execution path and every possible input
- it tests the functionality of the units themselves, so it will not catch integration errors
- for every line of code written, programmers often need 3 to 5 lines of test code.[2]. Is this effort worth the goal?
- nondeterministic or multiple threads modules are very difficult to test
- effort to guarantee discipline trough the software development process (recording the unit tests and their execution) can be expensive
A very usefult framework to support java unit testing is junit. JUnit is linked as a JAR at compile-time; the framework resides under package under package org.junit for JUnit 4 and later.
Using JUnit, test methods must be annotated by the @Test
annotation.
package education.jtrainer.tutorial.junit; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class ReserveCarTest { @Test void reserveCarSuccess() { try { ReservationService reservationService=new ReservationService(); @SuppressWarnings("unused") Reservation reservation=reservationService.getReservation("MINI"); } catch (Exception e) { fail("Error during reservation creation"); } } @Test void reserveCarException() { try { ReservationService reservationService=new ReservationService(); @SuppressWarnings("unused") Reservation reservation=reservationService.getReservation("VAN"); } catch (Exception e) { fail("Error during reservation creation"); } } }
For running this two test method with eclipse, simply hover the mouse on the class and after clicking on the right button go to Run As…..Junit Test
The above class tests a service: the ReservationService with two different parameter. The class code is the following:
package education.jtrainer.tutorial.junit; public class ReservationService { public CarFactory carFactory; public ReservationService() { carFactory=new CarFactory(); } public Reservation getReservation(String carType) throws Exception { Car reservedCar=carFactory.getCar(carType); if (reservedCar==null) throw new Exception("car type not available"); Reservation reservation=new Reservation(); reservation.setReservedCar(reservedCar); return reservation; } public CarFactory getCarFactory() { return carFactory; } public void setCarFactory(CarFactory carFactory) { this.carFactory = carFactory; } }
It uses a Factory class CarFactory to get a Car and the set a Reservation object. The factory can return null or a car object depending on the car type requested, checked again an Enum Type:
package education.jtrainer.tutorial.junit; public class CarFactory { public Car getCar(String carType){ for (CarType m : CarType.values()) { if (m.toString().equals(carType)) { return new Car(m); } } return null; } }
The car object is defined as follows:
package education.jtrainer.tutorial.junit; public class Car { CarType carType; public Car(CarType carType) { this.carType=carType; } }
Where CarType is an Enum type:
package education.jtrainer.tutorial.junit; public enum CarType { SMALL, MEDIUM, MINI, COMPACT, ECONOMY }
The Reservation class is the following:
In this basic example we can see that
- each test cases is indipendent from the other (reserveCarSuccess va reserveCarException)
- the two test cases allow developers to see if they understood which parameter/s allow the correct instation of a reservation object
- each test case tests the creation of the reservation object in isolation also thanks to the Enum Type
- regression testing and also refactoring the code is easier because once i have all the test case, is easy to run them after a refactoring process of the reservation creation object (for example i could check the car type against a table in database)
- If i write all the unit test for all the parameter (SMALL, MEDIUM, MINI, etc…) this gives a basic understanding of the unit’s interface (API) so this information can be used for documentation
Let’s now suppose our project is spring-based. The test class changes as follows:
package education.jtrainer.tutorial.junit; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:application-context.xml"}) public class ReserveCarTest { @Autowired ReservationService reservationService; @Test public void reserveCarSuccess() { try { @SuppressWarnings("unused") Reservation reservation=reservationService.getReservation("MINI"); } catch (Exception e) { fail("Error during reservation creation"); } } @Test public void reserveCarException() { try { @SuppressWarnings("unused") Reservation reservation=reservationService.getReservation("VAN"); } catch (Exception e) { fail("Error during reservation creation"); } } }
In order for the unit test to run a batch job, the framework must load the job’s ApplicationContext. Two annotations are used to trigger this:
@RunWith(SpringJUnit4ClassRunner.class):
Indicates that the class should use Spring’s JUnit facilities
@ContextConfiguration(locations = {...}):
Indicates which XML files contain the ApplicationContext.
The application-context.xml
is defined as follows:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config /> <bean id="carFactory" class="education.jtrainer.tutorial.junit.CarFactory"></bean> <bean id="reservationService" class="education.jtrainer.tutorial.junit.ReservationService"></bean> </beans>
The reservationService bean is quite different from before as it uses spring ApplicationContext
(trough the @Autowired
annotation):
package education.jtrainer.tutorial.junit; import org.springframework.beans.factory.annotation.Autowired; public class ReservationService { @Autowired public CarFactory carFactory; public Reservation getReservation(String carType) throws Exception { Car reservedCar=carFactory.getCar(carType); if (reservedCar==null) throw new Exception("car type not available"); Reservation reservation=new Reservation(); reservation.setReservedCar(reservedCar); return reservation; } public CarFactory getCarFactory() { return carFactory; } public void setCarFactory(CarFactory carFactory) { this.carFactory = carFactory; } }
The Car, CarFactory, CarType classes are exactly the same as in the no-spring example.
Code tested with jdk 1.8.0_151, junit 5.0.0, spring 4.3.3