The term Agile, in the world of software development, typically refers to an approach to project management that aims to unite teams around the principles of collaboration, simplicity, flexibility, and responsiveness throughout the process of developing a new program in an application.
An Agile software testing means the practice of testing software for any performance issues or bugs within the context of Agile workflow. The developers and testers, in the agile approach, are seen as the two sides of the same coin. The Agile software testing includes unit testing and integration testing. It helps with executing the tests as quickly as possible.
Let's understand the significance and the objectives of unit and integration testing.
Unit testing, as the name suggests, is the testing of every individual method of the code. It is the method of testing the fundamental pieces of your functionality. It is a piece of code written by the software developer to test a specific functionality of the code. Unit tests are used for improving the quality of the code and preventing bugs. They are not commonly used for finding them. They are automated testing frameworks.
Let's take an example of the EmployeeService
class that needs the employeeDao
object for loading the data from the database. This employeeDao
is a real object. So, to test the EmployeeService
class, it is required to provide the employeeDao
object that has a valid connection to the database. We also have to insert the data needed for the test into the database.
Inserting the data into the database after setting up the connection and then testing on an actual database can be a lot of work. Instead, we can provide the EmployeeService
instance with a fake EmployeeDao
class, which will just return the data that we need to complete the test. This fake EmployeeDao
class will not read any data from the database. This fake EmployeeDao
class is a mock object that is a replacement for a real object, which makes it easier to test the EmployeeService
class.
A common technique that can be applied while testing a unit that depends on other units is to simulate the unit's dependencies with stubs and mock objects, which help in reducing the complexity because of the dependencies in the unit test. Let's look at each of them in detail:
State verification is used to check whether the actual method returns the correct value. Behavior verification is used to check whether the correct method was called. Stub is used for state verification, whereas a mock object is used for behavior verification. A stub object cannot fail a unit test but a mock object can. This is because we know what and why we are implementing a stub object, whereas a mock is just a fake object that mimics a real object and if the business logic in the code is wrong, then the unit test fails even if we have passed a real object.
Unit testing is easy for the isolated class, which tests either the class or its method in isolation. Let's create unit tests for the isolated class, where the class under testing will not directly depend on any other class, as shown in the following diagram:
The core functions of the HrPayroll
system should be designed around employee details. First, we need to create the Employee
class and override the equals()
method, as shown in the following code snippet:
package org.packt.Spring.chapter9.SpringTesting.modle; public class Employee { private String employeeId; private String firstName; private String lastName; private int salary; // constructor, Getters and setters @Override public boolean equals(Object obj) { if (!(obj instanceof Employee)) { return false; } Employee employee = (Employee) obj; return employee.employeeId.equals(employeeId); } }
Now, to persist the employee
object to the HrPayroll
system, we need to define the EmployeeDao
interface:
package org.packt.Spring.chapter9.SpringTesting.dao; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; public interface EmployeeDao { public void createEmployee(Employee employee); public void updateEmployee(Employee employee); public void deleteEmployee(String employeeId); public Employee findEmployee(String employeeId); }
Let's implement the EmployeeDao
interface to demonstrate the unit testing for this isolated class:
package org.packt.Spring.chapter9.SpringTesting.dao; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; public class InMemeoryEmployeeDaoImpl implements EmployeeDao { private Map<String, Employee> employees; public InMemeoryEmployeeDaoImpl() { employees = Collections .synchronizedMap(new HashMap<String, Employee>()); } public boolean isOldEmployee(String employeeId) { return employees.containsKey(employeeId); } @Override public void createEmployee(Employee employee) { if (!isOldEmployee(employee.getEmployeeId())) { employees.put(employee.getEmployeeId(), employee); } } @Override public void updateEmployee(Employee employee) { if (isOldEmployee(employee.getEmployeeId())) { employees.put(employee.getEmployeeId(), employee); } } @Override public void deleteEmployee(String employeeId) { if (isOldEmployee(employeeId)) { employees.remove(employeeId); } } @Override public Employee findEmployee(String employeeId) { return employees.get(employeeId); } }
From the aforementioned code snippet, we can see that the InMemeoryEmployeeDaoImpl
class doesn't depend on any other class directly, which makes it easier to test, because we don't need to be worried about setting dependency and their working.
Here is an implementation of InMemeoryEmployeeDaoTest
:
package org.packt.Spring.chapter9.SpringTesting.test; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.packt.Spring.chapter9.SpringTesting.dao.InMemeoryEmployeeDaoImpl; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; public class InMemeoryEmployeeDaoTest { private static final String OLD_EMPLOYEE_ID = "12121"; private static final String NEW_EMPLOYEE_ID = "53535"; private Employee oldEmployee; private Employee newEmployee; private InMemeoryEmployeeDaoImpl empDao;
The setUp()
method is annotated with the @Before
annotation, as shown in the code snippet here:
@Before public void setUp() { oldEmployee = new Employee(OLD_EMPLOYEE_ID, "Ravi", "Soni", 1001); newEmployee = new Employee(NEW_EMPLOYEE_ID, "Shashi", "Soni", 3001); empDao = new InMemeoryEmployeeDaoImpl(); empDao.createEmployee(oldEmployee); }
The isOldEmployeeTest()
method is annotated by the @Test
annotation. This test method verifies the employeeId
, as shown in the following code snippet:
@Test public void isOldEmployeeTest() { Assert.assertTrue(empDao.isOldEmployee(OLD_EMPLOYEE_ID)); Assert.assertFalse(empDao.isOldEmployee(NEW_EMPLOYEE_ID)); }
The createNewEmployeeTest()
method is annotated by the @Test
annotation. This test method creates a new employee and then verifies the new employeeId
:
@Test public void createNewEmployeeTest() { empDao.createEmployee(newEmployee); Assert.assertTrue(empDao.isOldEmployee(NEW_EMPLOYEE_ID)); }
The updateEmployeeTest()
method is annotated by the @Test
annotation. This test method updates employee details and then verifies the employee's firstName
, as shown here:
@Test public void updateEmployeeTest() { String firstName = "Sharee"; oldEmployee.setFirstName(firstName); empDao.updateEmployee(oldEmployee); Assert.assertEquals(firstName, empDao.findEmployee(OLD_EMPLOYEE_ID) .getFirstName()); }
The deleteEmployeeTest()
method is annotated by the @Test
annotation. This test method deletes employee details and then verifies the employee ID, as shown in the following code snippet:
@Test public void deleteEmployeeTest() { empDao.deleteEmployee(OLD_EMPLOYEE_ID); Assert.assertFalse(empDao.isOldEmployee(OLD_EMPLOYEE_ID)); } }
The test results of the aforementioned test cases will be as shown here:
As we have seen in the previous section, testing either an isolated class or an independent class is easy. However, it would be a little more difficult to test a class that depends on another class, such as the EmployeeService
class (that holds business logic), which depends on the EmployeeDao
class (this class knows how to communicate with the database and get the information). Unit testing is harder and has dependencies, as shown here:
Class Under Test means that whenever we write a unit test, generally the term "unit" refers to a single class against which we have written the tests. It is the class that is being tested. So it's good to remove the dependencies, create a mock object and continue with the unit testing, as shown in the following diagram:
The concept behind removing the dependencies and creating a mock object is that by creating an object that can take the place of a real dependent object. If we are writing a unit test for our EmployeeService
around business logic, then that particular unit test should not connect EmployeeService
to the EmployeeDao
intern, and then connect the EmployeeDao
intern to the database and perform a crud operation, because we just want to perform the testing of the EmployeeService
class, and so we need to create a mock EmployeeDao
. The Mockito framework allows us to create the mock object.
The Mockito framework is an open source mock framework for unit testing; it was originally based on EasyMock, which can be downloaded from either http://mockito.org/ or https://code.google.com/p/mockito/. It can be used in conjunction with other testing tools, such as JUnit. It helps in creating and configuring mock objects. Add the Mockito JAR to your CLASSPATH
along with JUnit. It uses the field-level annotations, as shown here:
@Mock
: This creates the mock object for an annotated field.@Spy
: This creates spies for the objects or the files it annotates.@InjectMocks
: The private field that is annotated by the @InjectMocks
annotations is instantiated and Mockito injects the fields annotated with either the @Mock
annotation or the @Spy
annotation to it.@RunWith(MockitoJUnitRunner.class)
: If you use the aforementioned annotations, then it is must be done to annotate the test class with this annotation to use the MockitoJUnitRunner
. When MockitoJUnitRunner
executes the unit tests, it creates mock objects and spy objects for all the fields annotated by the @Mock
annotation or the @Spy
annotation.Let's perform the unit testing using Mockito, where we create a mock object for a dependent object. Here is the code for the EmployeeService.java
interface:
package org.packt.Spring.chapter9.SpringTesting.service; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; public interface EmployeeService { public Employee findEmployee(String employeeId); }
The following is an implementation of EmployeeService
:
package org.packt.Spring.chapter9.SpringTesting.service; import org.packt.Spring.chapter9.SpringTesting.dao.EmployeeDao; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao = null; public EmployeeServiceImpl(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } @Override public Employee findEmployee(String employeeId) { return employeeDao.findEmployee(employeeId); } }
And, here we have created our test class in the test
folder, and created a mock object by annotating EmployeeDao
. We have annotated the class by the @RunWith(MockitoJUnitRunner.class)
annotation. We have created two test methods by using the @Test
annotation, where, in the first test case, we verify that the findEmployee
behavior happened once and in the second test case, we verify that no interactions happened on employeeDao
mocks:
package org.packt.Spring.chapter9.SpringTesting.service; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.packt.Spring.chapter9.SpringTesting.dao.EmployeeDao; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; @RunWith(MockitoJUnitRunner.class) public class EmployeeServiceTest { private static final String OLD_EMPLOYEE_ID = "12121"; private Employee oldEmployee; private EmployeeService employeeService; @Mock private EmployeeDao employeeDao; @Before public void setUp() { employeeService = new EmployeeServiceImpl(employeeDao); oldEmployee = new Employee(OLD_EMPLOYEE_ID, "Ravi", "Soni", 1001); } @Test public void findEmployeeTest() { when(employeeDao.findEmployee(OLD_EMPLOYEE_ID)).thenReturn(oldEmployee); Employee employee = employeeService.findEmployee(OLD_EMPLOYEE_ID); Assert.assertEquals(oldEmployee, employee); // Verifies findEmployee behavior happened once verify(employeeDao).findEmployee(OLD_EMPLOYEE_ID); // asserts that during the test, there are no other calls to the mock // object. verifyNoMoreInteractions(employeeDao); } @Test public void notFindEmployeeTest() { when(employeeDao.findEmployee(OLD_EMPLOYEE_ID)).thenReturn(null); Employee employee = employeeService.findEmployee(OLD_EMPLOYEE_ID); Assert.assertNotSame(oldEmployee, employee); verify(employeeDao).findEmployee(OLD_EMPLOYEE_ID); // Verifies that no interactions happened on employeeDao mocks verifyZeroInteractions(employeeDao); verifyNoMoreInteractions(employeeDao); } }
Integration testing is a phase of software testing in which individual software modules are combined and tested as a group to ensure that the required units are properly integrated and interact correctly with each other. The purpose of integration testing is to verify the functionality, performance, and reliability of the code. Integration testing is used for testing several units together.
Let's take an example. We can create an integration test to test EmployeeServiceImpl
using InMemeoryEmployeeDaoImpl
as a DAO implementation:
package org.packt.Spring.chapter9.SpringTesting.service; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.packt.Spring.chapter9.SpringTesting.dao.EmployeeDao; import org.packt.Spring.chapter9.SpringTesting.dao.InMemeoryEmployeeDaoImpl; import org.packt.Spring.chapter9.SpringTesting.modle.Employee; public class EmployeeServiceIntegrationTest { private static final String OLD_EMPLOYEE_ID = "12121"; private static final String NEW_EMPLOYEE_ID = "53535"; private Employee oldEmployee; private Employee newEmployee; private EmployeeService employeeService; @Before public void setUp() { oldEmployee = new Employee(OLD_EMPLOYEE_ID, "Ravi", "Soni", 1001); newEmployee = new Employee(NEW_EMPLOYEE_ID, "Shashi", "Soni", 3001); employeeService = new EmployeeServiceImpl( new InMemeoryEmployeeDaoImpl()); employeeService.createEmployee(oldEmployee); } @Test public void isOldEmployeeTest() { Assert.assertTrue(employeeService.isOldEmployee(OLD_EMPLOYEE_ID)); Assert.assertFalse(employeeService.isOldEmployee(NEW_EMPLOYEE_ID)); } @Test public void createNewEmployeeTest() { employeeService.createEmployee(newEmployee); Assert.assertTrue(employeeService.isOldEmployee(NEW_EMPLOYEE_ID)); } @Test public void updateEmployeeTest() { String firstName = "Sharee"; oldEmployee.setFirstName(firstName); employeeService.updateEmployee(oldEmployee); Assert.assertEquals(firstName, employeeService.findEmployee(OLD_EMPLOYEE_ID).getFirstName()); } @Test public void deleteEmployeeTest() { employeeService.deleteEmployee(OLD_EMPLOYEE_ID); Assert.assertFalse(employeeService.isOldEmployee(OLD_EMPLOYEE_ID)); } }