Data Driven Testing – Decoupling test data from test logic

Data-driven testing (DDT) is a term used in the testing of computer software to describe testing done using a table of conditions directly as test inputs and verifiable outputs as well as the process where test environment settings and control are not hard-coded. In the simplest form the tester supplies the inputs from a row in the table and expects the outputs which occur in the same row. The table typically contains values which correspond to boundary or partition input spaces. In the control methodology, test configuration is “read” from a database.

Anything that has a potential to change is separated out from the test logic and moved into an ‘external asset’. This can be a configuration or test dataset. The logic executed in the script is dictated by the data values. The script is simply a “driver” for the data that is held in the data source.

To convey the concept, I’ve created a sample project using TestNG. Pl. note implementation minimized for brevity. You will need following jars in your class path to successfully execute the project:

  1. poi-3.12-beta1-20150228.jar
  2. poi-ooxml-3.12-beta1-20150228.jar
  3. testng-6.8.21.jar
  4. xmlbeans-2.6.0.jar

The above were the versions which I had used for the demonstration. You could use the latest version of the above jars.

I’ve put the test data & expected outcome in an excel called TestData.xls and in my test case I refer it. The excel has multiple sheets which holds data for each test case type.

Below is the source code under test –

package com.sanjit;

/**
 * A simple minimal calculator. Implementation minmized for brevity.
 *
 * @author Sanjit Mohanty
 * @version 0.1
 *
 *
 * Revision History:
 * VERSION DATE AUTHOR COMMENT
 * 0.1 23-Apr-2015 Sanjit Mohanty initial create
 *
 *
 */

public class Calculator {

 public Calculator(){
 }

 public Double sum(Double obj1, Double obj2){
 return obj1+obj2;
 }

 public Double diff(Double obj1, Double obj2){
 return obj1-obj2;
 }

 public Double mul(Double obj1, Double obj2){
 return obj1*obj2;
 }

 public Double div(Double obj1, Double obj2){
 return obj1/obj2;
 }
}

Below is the unit test source code for the above class. Here we refer the excel for input data as well as for the expected outcome. No test data is mentioned here. The same test source runs for different inputs.

</pre>
<pre>package com.sanjit.test;

import java.lang.reflect.Method;

import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.sanjit.Calculator;
import com.sanjit.test.util.DataTypeParameter;
import com.sanjit.test.util.ExcelUtils;

/**
 * Test class for
 * {@link com.sanjit.Calculator}
 *
 * A simple example of Data Driven Testing!
 * Test data has been externalized to an excel and is being fed to the test case through TestNG's dataprovider.
 *
 * @author Sanjit Mohanty
 * @version 0.1
 *
 *
 * Revision History:
 * VERSION DATE AUTHOR COMMENT
 * 0.1 23-Apr-2015 Sanjit Mohanty initial create
 *
 *
 */

public class CalculatorTest {
 private Calculator calculator;

 @BeforeMethod
 public void initialize() {
 calculator = new Calculator();
 }

 /*
 * Data Provider feeding test data from an excel with multiple sheets for different test cases
 */
 @DataProvider(name = "testData")
 public Object[][] testData(final Method testMethod) throws Exception {
 DataTypeParameter parameters = testMethod
 .getAnnotation(DataTypeParameter.class);

 Object[][] testObjArray = ExcelUtils
 .getTableArray(
 "D://test//data//TestData.xls",
 parameters.testType()); // Edit the test data excel location accordingly

 return (testObjArray);

 }

 /*
 * Sum Test Case
 */
 @Test(dataProvider = "testData")
 @DataTypeParameter(testType = "SumTest")
 public void testSumChecker(String inputNumber1, String inputNumber2,
 String expectedResult) {
 Assert.assertEquals(
 Double.valueOf(expectedResult),
 calculator.sum(Double.valueOf(inputNumber1),
 Double.valueOf(inputNumber2)));
 }

 /*
 * Difference Test Case
 */
 @Test(dataProvider = "testData")
 @DataTypeParameter(testType = "DiffTest")
 public void testSubChecker(String inputNumber1, String inputNumber2,
 String expectedResult) {
 Assert.assertEquals(
 Double.valueOf(expectedResult),
 calculator.diff(Double.valueOf(inputNumber1),
 Double.valueOf(inputNumber2)));
 }

 /*
 * Multiplication Test Case
 */
 @Test(dataProvider = "testData")
 @DataTypeParameter(testType = "MulTest")
 public void testMulChecker(String inputNumber1, String inputNumber2,
 String expectedResult) {
 Assert.assertEquals(
 Double.valueOf(expectedResult),
 calculator.mul(Double.valueOf(inputNumber1),
 Double.valueOf(inputNumber2)));
 }

 /*
 * Division Test Case
 */
 @Test(dataProvider = "testData")
 @DataTypeParameter(testType = "DivTest")
 public void testDivChecker(String inputNumber1, String inputNumber2,
 String expectedResult) {
 Assert.assertEquals(
 Double.valueOf(expectedResult),
 calculator.div(Double.valueOf(inputNumber1),
 Double.valueOf(inputNumber2)));
 }
}

Below I’ve created a custom annotation to inject parameter into the TestNG data provider. In my case this annotation holds the test case type like SumTest, DiffTest, MulTest etc. Based on the test case type, the corresponding sheet in the excel is parsed by the test source code for the test run.

</pre>
<pre>package com.sanjit.test.util;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

/**
 * Custom annotation for parameter passing to the data provider.
 *
 * @author Sanjit Mohanty
 * @version 0.1
 *
 *
 * Revision History:
 * VERSION DATE AUTHOR COMMENT
 * 0.1 23-Apr-2015 Sanjit Mohanty initial create
 *
 *
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataTypeParameter {
 String testType();
}

Below is a utility class for reading the excel sheet which has our test data as well as the expected outcome.

<pre>package com.sanjit.test.util;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.poi.ss.usermodel.WorkbookFactory;

/**
 * Excel parsing utility class
 *
 * @author Sanjit Mohanty
 * @version 0.1
 *
 *
 * Revision History:
 * VERSION DATE AUTHOR COMMENT
 * 0.1 23-Apr-2015 Sanjit Mohanty initial create
 *
 *
 */

public class ExcelUtils {

 private static org.apache.poi.ss.usermodel.Workbook ExcelWBook;
 private static org.apache.poi.ss.usermodel.Sheet ExcelWSheet;

 private static org.apache.poi.ss.usermodel.Cell Cell;
 private static org.apache.poi.ss.usermodel.Row Row;

 public static Object[][] getTableArray(String FilePath, String SheetName)
 throws Exception {

 String[][] tabArray = null;

 try {

 int startRow = 1;
 int startCol = 1;
 int totalCols = 0;
 int totalRows = 0;
 int ci, cj;

 FileInputStream ExcelFile = new FileInputStream(FilePath);

 // Access the required test data sheet
 ExcelWBook = WorkbookFactory.create(ExcelFile);
 ExcelWSheet = ExcelWBook.getSheet(SheetName);

 totalRows = ExcelWSheet.getLastRowNum() + 1;
 if (totalRows >= 0) {
 totalCols = ExcelWSheet.getRow(0).getPhysicalNumberOfCells();
 }

 tabArray = new String[totalRows][totalCols];
 ci = 0;

 for (int i = startRow; i <= totalRows; i++, ci++) {

 cj = 0;

 for (int j = startCol; j <= totalCols; j++, cj++) {

 tabArray[ci][cj] = getCellData(i - 1, j - 1);

 System.out.println(tabArray[ci][cj]);

 }

 }
 }

 catch (FileNotFoundException e) {

 System.out.println("Could not read the Excel sheet");

 e.printStackTrace();

 }

 catch (IOException e) {

 System.out.println("Could not read the Excel sheet");

 e.printStackTrace();

 }

 return (tabArray);

 }

 public static String getCellData(int RowNum, int ColNum) throws Exception {

 try {

 Cell = ExcelWSheet.getRow(RowNum).getCell(ColNum);

 int dataType = Cell.getCellType();

 if (dataType == Cell.CELL_TYPE_NUMERIC) {
 return String.valueOf(Cell);

 } else if (dataType == Cell.CELL_TYPE_BLANK) {

 return "";
 } else if (dataType == Cell.CELL_TYPE_STRING) {

 String CellData = Cell.getStringCellValue();

 return CellData;

 }
 } catch (Exception e) {

 System.out.println(e.getMessage());

 throw (e);

 }
 return null;

 }
}