One of the greatest features of the TestNG framework is a
DataProvider (http://testng.org/doc/documentation-main.html#parameters-dataproviders).
It is worth to use the DataProvider to parameter Selenium based tests to get better flexibility.
Here I am sharing my experience with the TestNG DataProvider.
The first task was to decide how to structure and store test data - an Excel document is a good data storage.
A java class with a test method that required external data should have a dedicated Excel file. The Excel file should have the same name as the class name including package.
The test method should have a dedicated tab in the Excel file. Tab’s name should be identical to method’s name.
For example there is a class com.qamation.tests.ApplicationFormTests with a test a method testFirstName. Then the data file should have name com.qamation.tests.ApplicationFormTests.xls (or xlsx). This test should have data in a tab named testFirstName.
The test method should have a dedicated tab in the Excel file. Tab’s name should be identical to method’s name.
For example there is a class com.qamation.tests.ApplicationFormTests with a test a method testFirstName. Then the data file should have name com.qamation.tests.ApplicationFormTests.xls (or xlsx). This test should have data in a tab named testFirstName.
This approach allows to separate test data for different
test environments using different folders. A mapping between environment and a folder can be using a property file. For example, two files with the same name com.qamation.tests.ApplicationFormTests.xls can be saved in different folders “sandbox”
and “production”.
In order to get data from an Excel document into a test, I am using Apache POI - the Java API for Microsoft Documents project. The library and documentation can be found at http://poi.apache.org/.
In most cases I am using two data providers/formats.
The first data provider treats each line in an excel spread sheet
as a test case data. Columns filled with data corresponds to
parameters in a test method. For example, the testMethod2 accepts four
parameters:
@Test(dataProvider =
"data" dataProviderClass =
com.qamation.data.provider.TestDataProvider.class)
public void testMethod2(String value1, String value2, String value3,
String
value4) {…}
It means that corresponding excel sheet should have data in
first 4 columns. The data file should have tab named testMethod2. First line of
the sheet is ignored – test data starts from line 2.
In the code, this data provider is annotated as
@DataProvider(name=”data”). In order to use this data provider annotate your test method with the following line:
@Test(dataProvider
= "data", dataProviderClass =
com.qamation.data.provider.TestDataProvider.class)The second data provider is more complicated. It is based on an idea that for a single input there are several verification points.
For example, when an application form is submitted, I would
like to verify that the result page has several expected components.
In addition, similar set of input data may result different outcomes. For example, submitting the same application form as above but with invalid data should result different outcomes.
I want to utilize the same test method, the same Excel document and sheet for more tests.
In order to implement that, I have introduced a “Test Data” block on an excel sheet.
A data block is a set of one or several lines with data. The left part of the
block is input and the right part of the block contains expected results. Number of
columns in the input and expected sections are defined in a data sheet header. The input data should not duplicated in a single data block.
The first three lines of the data sheet form a data sheet
header.
Cell a1 may contain a description of value in a2, for example: number of input fields.
Cell b1 may contain a description of value in b2, for example: number of fields for expected results.
Finally, Cell c1 may contain a description of value in c2, for example: total test data lines.
Cell a1 may contain a description of value in a2, for example: number of input fields.
Cell b1 may contain a description of value in b2, for example: number of fields for expected results.
Finally, Cell c1 may contain a description of value in c2, for example: total test data lines.
The following picture shows three data blocks for a login test. Each data block provides login form input data. The “expected” results contains information that may be used by WebDriver or WebElement findElement(By arg0) to verify a text on the result page.
First three lines of the data
sheet form the header. A data block starts with a line where input columns are not empty.
Any line of data starting with “!--" will be ignored. The string “!--" in the beginning of a data block will result ignoring the whole data block.
Any line of data starting with “!--" will be ignored. The string “!--" in the beginning of a data block will result ignoring the whole data block.
The picture above shows three data blocks.
The first block starts from
line 4 with input fields username and password. Line 6 of this data block will
be ignored.
The second block starting at
line 8 will be ignored.
The fird data block starts
from line 10.
In order to use the second data format, add the following annotation to your test method:
@Test(dataProvider
= "inputAndOutput", dataProviderClass =
com.qamation.data.provider.TestDataProvider.class)Now let’s look at code that allows to use those data providers.
A data provider method for
first format is:
@DataProvider(name = "data")
public static String[][]
getData(Method testMethod) throws IOException {
int numberOfParameters =
testMethod.getParameterTypes().length;
return getDataForClassName(testMethod.getDeclaringClass().getName(),
testMethod.getName(),
numberOfParameters);
}
getDataForClassName goes
through several steps including find the excel file,
reads it and fills a String[][] to return as the desired data.
Here are two methods that go through excel cells:
private static String[][]
fillData(Sheet sheet, int startRow, int rows,
int startColumn, int endColumn) {
if (startRow < 1)
throw new RuntimeException("startRow
cannot be less than 1");
if (startColumn < 1)
throw new RuntimeException("startColumn
cannot be less than 1");
if (endColumn <
startColumn)
throw new RuntimeException(
"endColumn
number cannot be less than startColumn number");
int numberOfParameters =
endColumn - startColumn + 1;
String[][]
returnValue = new String[rows][numberOfParameters];
int ii = 0;
for (int i = startRow - 1; i
< startRow + rows - 1; i++) {
Row
row = sheet.getRow(i);
if (row == null) {
return returnValue;
}
int jj = 0;
for (int j = startColumn - 1;
j < endColumn; j++) {
Cell
cell = row.getCell(j);
returnValue[ii][jj]
= getCellValue(cell);
jj++;
}
ii++;
}
return returnValue;
}
private static String getCellValue(final Cell cell) {
if (cell == null) return "";
int cellType =
cell.getCellType();
switch (cellType) {
case Cell.CELL_TYPE_NUMERIC:
if (DateUtil.isCellDateFormatted(cell))
{
return String.valueOf(cell.getDateCellValue());
}
else {
return String.valueOf(cell.getNumericCellValue());
}
case Cell.CELL_TYPE_STRING:
return
cell.getStringCellValue().trim();
case Cell.CELL_TYPE_BOOLEAN:
return Boolean.toString(cell.getBooleanCellValue());
default:
return "";
}
}
The
following is a data provider for to support the second data format:
@DataProvider(name = "inputAndOutput")
public static DataBlock[][]
getTestInformation(Method testMethod)
throws IOException {
DataBlock[][]
testData = getTestData(testMethod);
return testData;
}
The actual formation of data blocks occurs
in the following method:
private static DataBlock[][] getTestData(Method testMethod)
throws IOException {
Sheet
sheet = getDataSheet(testMethod);
Row
testProps = sheet.getRow(1);
String
str = getCellValue(testProps.getCell(0));
int inputColumns = (int) Float.parseFloat(str);
str
= getCellValue(testProps.getCell(1));
int expectationColumns =
(int) Float.parseFloat(str);
str
= getCellValue(testProps.getCell(2));
int totalDataRows = (int) Float.parseFloat(str);
int totalDataColumns =
inputColumns + expectationColumns;
String[][]
data = fillData(sheet, 4, totalDataRows, 1, totalDataColumns);
ArrayList<DataBlock>
dataList = new ArrayList<DataBlock>();
DataBlock
td = null;
int j = -1;
boolean dataBlockIsActive = true;
for (int i = 0; i < data.length; i++) {
String[]
dataLine = data[i];
if (dataLine[0]==null) break;
if
(dataLine[0].startsWith("!--") && dataLine[0].length() > 3) {
dataBlockIsActive
= false;
continue;
}
else if (dataLine[0].startsWith("!--")) continue;
else if (!dataBlockIsActive &&
dataLine[0].length()==0) continue;
else dataBlockIsActive = true;
else dataBlockIsActive = true;
if (dataBlockIsActive) {
String[]
input = new String[inputColumns];
String[]
expected = new String[expectationColumns];
System.arraycopy(dataLine,
inputColumns, expected, 0,
expectationColumns);
if
(dataLine[0].length() > 0) {
if (j >= 0)dataList.add(j,
td);
j++;
System.arraycopy(dataLine,
0, input, 0, inputColumns);
td
= new DataBlock(i + 4,
input, expected);
}
else if (td != null) {
td.addExpectedResult(expected);
}
}
}
if (td != null) dataList.add(j,
td);
DataBlock[][]
testData = new DataBlock[dataList.size()][1];
for (int i = 0; i <
dataList.size(); i++) {
testData[i][0]
= dataList.get(i);
}
return testData;
}
Use this link to get the full version of the TestDataProvider class:
Full version of the TestDataProvider class
Full version of the TestDataProvider class

