Skip to content

Latest commit

 

History

History
502 lines (436 loc) · 25.8 KB

README.md

File metadata and controls

502 lines (436 loc) · 25.8 KB

TestNg-DataProviders BuildStatus

This project exercises testng dataProviders backed by various Office formats

Unlike core TestNg data providers configurable through annotation constant parameters this Data provider class features runtime-flexible data file paths iparameterization enabling one running the jar with environment-specific test data without recompiling the java project. This feature was requested in one of the forums and was easy to implement - details in in Extra Features section below.

Testing

For example test case performs Selenium link count test with the data providers of the following supported data types:

  • Excel 2003
  • Excel 2007
  • Open Office Spreadsheet
  • JSON

The test inputs are defined as spreadsheet with columns

ROWNUM SEARCH COUNT
1 junit 100

or a JSON file with the following structure:

{
    "test": [{
        "keyword": "junit",
        "count": 101.0,
        "comment": "",
        "other unused column": "",
    }, {
        "comment": "",
        "keyword": "testng",
        "count": 31.0
    },
...
]
}

which represent the test id, the seach term and expected minimum count of articles found on the forum through title search.

The following annotations are provided to the test methods:

@Test(enabled = true, singleThreaded = false, threadPoolSize = 1, invocationCount = 1, description = "searches publications for a keyword", dataProvider = "Excel 2003")
@DataFileParameters(name = "data_2003.xls", path = "${USERPROFILE}\\Desktop", sheetName = "Employee Data")
	public void test_with_Excel_2003(double rowNum, String search_keyword,
			double expected_count) throws InterruptedException {
		parseSearchResult(search_keyword, expected_count);
	}

or

@Test(enabled = true, singleThreaded = false, threadPoolSize = 1, invocationCount = 1, description = "searches publications for a keyword", dataProvider = "Excel 2007")
@DataFileParameters(name = "data_2007.xlsx", path = ".")
	public void test_with_Excel_2007(double rowNum, String search_keyword,
			double expected_count) throws InterruptedException {
		parseSearchResult(search_keyword, expected_count);
	}

or

@Test(enabled = true, singleThreaded = false, threadPoolSize = 1, invocationCount = 1, description = "searches publications for a keyword", dataProvider = "OpenOffice Spreadsheet")
@DataFileParameters(name = "data.ods", path = "src/main/resources") // when datafile path is relative assume it is under ${user.dir}
	public void test_with_OpenOffice_Spreadsheet(double rowNum,
			String search_keyword, double expected_count)
			throws InterruptedException {
		parseSearchResult(search_keyword, expected_count);
	}

or

@Test(enabled = true, singleThreaded = false, threadPoolSize = 1, invocationCount = 1, description = "searches publications for a keyword", dataProvider = "JSON")
@JSONDataFileParameters(name = "data.json", dataKey = "test", columns = "keyword,count"
  // one need to list value columns explicitly with JSON due to the way org.json.JSONObject is implemented
	public void test_with_JSON(String search_keyword, double expected_count)
			throws InterruptedException {
		parseSearchResult(search_keyword, expected_count);
	}

The data provider class will load all columns from Excel 2003, Excel 2007 or OpenOffice spreadsheet respectively and columns defined for JSON data provider and run test method with every row of data. It is up to the test developer to make the test method consume the correct number and type or parameters as the columns in the spreadsheet.

To enable debug messages during the data loading, set the debug flag with @DataFileParameters attribute:

	@Test(enabled = true, singleThreaded = true, threadPoolSize = 1, invocationCount = 1, description = "# of articless for specific keyword", dataProvider = "Excel 2007", dataProviderClass = ExcelParametersProvider.class)
	@DataFileParameters(name = "data_2007.xlsx", path = ".", sheetName = "Employee Data", debug = true)
	public void test_with_Excel_2007(double rowNum, String searchKeyword,
			double expectedCount) throws InterruptedException {
		dataTest(searchKeyword, expectedCount);
	}

this will show the following:

Data Provider Caller Suite: Suite 1
Data Provider Caller Test: Parse Search Result
Data Provider Caller Method: test_with_Excel_2007
0 = A ID
1 = B SEARCH
2 = C COUNT
Cell Value: 1.0 class java.lang.Double
Cell Value: junit class java.lang.String
Cell Value: 104.0 class java.lang.Double
...
row 0 : [1.0, junit, 104.0]
...

Extra Features

This data provider overcomes the known difficulty of core TestNG or Junit parameter annotations: developer is not allowed to redefine the dataprovider attributes like for example the data source path:

  public static final String dataPath = "src/main/resources";

@Test(enabled = true, dataProvider = "OpenOffice Spreadsheet", dataProviderClass = ExcelParametersProvider.class)
  @DataFileParameters(name = "data.ods", path = dataPath, debug = false)
  public void test(double rowNum,
    String searchKeyword, double expectedCount) throws InterruptedException {
    // actual code ot the  test
  }

In the above, one is only allowed to initialize the dataPath to a String (or int) primitive type, in particular even declaring the same (pseudo-const) value in a separate class:

public class ParamData {
  public final static String dataPath = "src/main/resources";
}

and assigning the result to the vatiable in the main test class,

public class FileParamsTest {
  private final static String dataPath = ParamData.dataPath;

or assigning the method rerturn value to the parameter:

  public final static String dataPath = param();

  public static final String param() {
    return "src/main/resources";
  }

would fail to compile:

Compilation failure:
[ERROR]TestNgDataProviderTest.java: element value must be a constant expression

so it likely not doable.

However it is quite easy to allow such flexibility in the data provider class ExcelParametersProvider itself by adding an extra class variable e.g. testEnvironment which would load its value from the environment variable named TEST_ENVIRONMENT

private final static String testEnvironment = (System
    .getenv("TEST_ENVIRONMENT") != null) ? System.getenv("TEST_ENVIRONMENT")
        : "";

and override the datafile path value provided in the test method annotation:

@Test(enabled = true, dataProvider = "OpenOffice Spreadsheet", dataProviderClass = ExcelParametersProvider.class)
@DataFileParameters(name = "data.ods", path = "src/main/resources", debug = false)
public void test(double rowNum, String searchKeyword, double expectedCount) throws InterruptedException {
  dataTest(searchKeyword, expectedCount);
}

in the presence of the environment TEST_ENVIRONMENT with the value dev will make it read parameters of the test from src/main/resources/dev/data.ods rather then src/main/resources/data.ods:

if (testEnvironment != null && testEnvironment != "") {
  filePath = amendFilePath(filePath);
}

This functionaliy is implemented directly in the ExcelParametersProvider provider class in a very basic fashion as shown below:

public class ExcelParametersProvider
implements ParametersProvider<ExcelParameters> {

  private final static String testEnvironment = (System.getenv("TEST_ENVIRONMENT") != null) ? System.getenv("TEST_ENVIRONMENT") : "";

  private static String amendFilePath(String filePath) {
    if (debug) {
      System.err.print(
        String.format("Amending the %s with %s", filePath, testEnvironment));       }
    // Inject the directory into the file path
    String updatedFilePath = filePath.replaceAll("^(.*)/([^/]+)$",
    String.format("$1/%s/$2", testEnvironment));
    if (debug) {
      System.err.println(String.format(" => %s", updatedFilePath));
    }
    return updatedFilePath;
}

and take it into account to redefine the inputs during initialization:

    filePath = String.format("%s/%s",
        (parameters.path().isEmpty()
            || parameters.path().equalsIgnoreCase("."))
                ? System.getProperty("user.dir")
                : Utils.resolveEnvVars(parameters.path()),
        parameters.name());
    // if the path is relative assume it is under ${user.dir}
    if (!filePath.matches("^[/\\\\].*")
        && !filePath.matches("^(?i:[A-Z]):.*")) {
      filePath = String.format("%s/%s", System.getProperty("user.dir"),
          filePath);
    }
    if (testEnvironment != null && testEnvironment != "") {
      filePath = amendFilePath(filePath);
    }

therefore the test

mkdir dev
mkdir src\main\resources\dev
copy src\main\resources\data.* src\main\resources\dev\
copy data*.* dev
set  TEST_ENVIRONMENT=dev
mvn test

works as expected (the example shows debug output for Open Office data file data.ods originally red from src/main/resources):

Amending the c:\developer\sergueik\testng-dataproviders/src/main/resources/data.
ods with dev => c:\developer\sergueik\testng-dataproviders/src/main/resources/de
v/data.ods
BeforeMethod Suite: Suite 1
Reading Open Office Spreadsheet: Employee Data
Cell Value: "1.0" class java.lang.Double
Cell Value: "junit" class java.lang.String
Cell Value: "202.0" class java.lang.Double
Cell Value: "2.0" class java.lang.Double

One can easily make this behavior optional, turn the TEST_ENVIRONMENT envirnmant name a separate parameter or switch to store definitions of environment specifics into the property file (this is work in progress). Similar changes will be soon available to

Caller-Specific Behavior

Sometimes a caller-specific behavior is required from custom data provider. See also accessing ITestContext in DataProvider(in Russian) This is easy to achieve, since the signature of the method one has to implement is:

	@DataProvider(parallel = false, name = "OpenOffice Spreadsheet")
	public static Object[][] createData_from_OpenOfficeSpreadsheet(final ITestContext context, final Method method) {
		if (debug) {
			System.err.println(String.format("Providing data to method: '%s' of test '%s'", method.getName(),
					context.getCurrentXmlTest().getName()));
		}

the test log then will include caller information:

Providing data to method: 'testWithOpenOfficeSpreadsheet' of test 'Parse Search Result'
Opening test
Reading Open Office Spreadsheet : test
...

Filtering Data Rows for JUnitParams

In addition to using every row of spreadsheet as test parameter one may create a designated column which value would be indicating to use or skip that row of data, like:

ROWNUM SEARCH COUNT ENABLED
1 junit 100 true
2 testng 30 true
3 spock 20 false
4 mockito 41 true

and annotate the method like

@Test(enabled = false, singleThreaded = true, threadPoolSize = 1, invocationCount = 1, description = "# of articless for specific keyword", dataProvider = "OpenOffice Spreadsheet", dataProviderClass = ExcelParametersProvider.class)
@DataFileParameters(name = "filtered_data.ods", path = dataPath, sheetName = "Filtered Example" , controlColumn = "ENABLED", withValue = "true", debug = true)
public void testWithFilteredParameters(double rowNum,
    String searchKeyword, double expectedCount) throws InterruptedException {
  dataTest(searchKeyword, expectedCount);
}

with this data setting only rows 1,2 and 4 from the data extract above would be used as testWithFilteredParameters test method parameters. The control column itself is not passed to the subject test method. Currently this functionality is implemented for OpenOffice spreadsheet only. Remaining format is a work in progress.

This feature of storing more then one set of tests in one spreadsheet and picking the ones which column is set to a specified value has been inspired by some python post and the forum(in Russian)

Note

When outside the project directory it is common to place the test datafile (like Excel spreadsheet) on Desktop, Downloads and other directories of the current user. However the dataprovider annotation parametes must be constant expressions and test method cannot use class variables or static methods like File.separator in annotation value, code like below will not compile:

Test( dataProvider = "Excel", dataProviderClass = ExcelParametersProvider.class)
@DataFileParameters(name = "data_2003.xls", path = (osName.startsWith("windows")) ? "${USERPROFILE}" : "${HOME}" + File.separator + "Desktop" )

To workaround this inconvenienve, the TestNg Data Providers internally converts between ${USERPRFILE} and ${HOME} and vise versa on Linux and Mac computers therefore the expressions path = "${USERPROFILE}\\Desktop" or path = "${HOMEDIR}/Downloads" work across OS.

Links

Maven Central

The snapshot versions are deployed to https://oss.sonatype.org/content/repositories/snapshots/com/github/sergueik/dataprovider/ The release versions status: Release pending

To use the snapshot version, add the following to pom.xml:

<dependency>
  <groupId>com.github.sergueik.testng</groupId>
  <artifactId>dataprovider</artifactId>
  <version>1.3-SNAPSHOT</version>
</dependency>
<repositories>
  <repository>
    <id>ossrh</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
  </repository>
</repositories>

Apache POI compatibility

  • The default version of the supported Apache POI is 3.17.
  • Older versions of the package require minor code refactoring. Note that you may also have to clear the other versions of poi and poi-ooxml jars from maven cache '~/.m2/repository'
  • Project can be built with Apache POI 4.0.0 by modifying poi.version to 4.0.0 within &lt;properties&gt; node in the pom.xml - the profile poi40 does not work correctly.
  • Creating branches and tags is a work in progress.

Google Sheet Data Provider

This is an experimental provider based on blog how to use Google Sheets API to read data from Spreadsheet.

The test method that is about to load the parameters from Google sheet is annotated in a similar fashion as with other providers developed in this project:

@Test(dataProviderClass = GoogleSheetParametersProvider.class, dataProvider = "Google Spreadsheet")
@DataFileParameters(name = "Google Sheets Example", path = "17ImW6iKSF7g-iMvPzeK4Zai9PV-lLvMsZkl6FEkytRg", sheetName = "Test Data", secretFilePath = "/home/sergueik/.secret/client_secret.json", debug = true)
public void testWithGoogleSheet(String strRowNum, String searchKeyword, String strExpectedCount)
    throws InterruptedException {
  double rowNum = Double.parseDouble(strRowNum);
  double expectedCount = Double.parseDouble(strExpectedCount);
  dataTest(searchKeyword, expectedCount);
	}

Here the name attibute stores the name of the application, path is for the id part of the data spreadsheet URL: https://docs.google.com/spreadsheets/d/${id}, and optional sheetName stores the name of the sheet.

Google Sheet

The path to secret file that is required to access the API, is to be defined through the secretFilePath attribute. Note: like with other attributes, the the value for annotation attribute DataFileParameters.secretFilePath must be a constant expression. The following would not compile:

  private static final String SECRET_FILEPATH = Paths
    .get(System.getProperty("user.home")).resolve(".secret")
    .resolve("client_secret.json").toAbsolutePath().toString();

  @Test(dataProviderClass = GoogleSheetParametersProvider.class, dataProvider = "Google Spreadsheet")
  @DataFileParameters(name = "Google Sheets Example", secretFilePath = SECRET_FILEPATH, ...)

but the following will:

  private static final String SECRET_FILEPATH = "C:/Users/Serguei/.secret/client_secret.json";

The secret file:

{
  "installed": {
    "client_id": "XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com",
    "project_id": "gogle-sheet-api-test-xxxxxxx",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "xxxxxxxxxxxxxxx",
    "redirect_uris": [
      "urn:ietf:wg:oauth:2.0:oob",
      "http://localhost"
    ]
  }
}

can be stored anywhere on disk outside of source control e.g. under ~/.secret/client_secret.json. It will be loaded once and the credential obtained from oauth would be cached and reused until expiration. The credential appears to be valid for approximately one hour. Currently the test opens the browser window prompting the user to confirm the access:

running test with Google Sheet Data Provider

In the future versions, parallel execution of Google Sheet parameterized tests and a more flexible caching of the access credentials is planned.

See Also

Note

  • upgrading to TestNg from version 6.14.3 to verson 7.5.1 breaks certain tests:
mvn -f pom.xml.BROKEN clean compile test-compile test
[ERROR] CommonTest.java:[55,41] cannot find symbol
[ERROR] symbol:   method getParameters()
[ERROR] location: class org.testng.xml.XmlTest
[ERROR] CommonTest.java:[164,49] cannot find symbol
[ERROR] symbol:   method getMethod()
[ERROR] location: variable testNGMethod of type org.testng.ITestNGMethod
[ERROR] CSVProviderTest.java:[62,33] cannot find symbol
[ERROR] symbol:   method getParameters()
[ERROR] location: class org.testng.xml.XmlTest

in the code

ITestResult iTestResult;
ITestNGMethod testNGMethod = iTestResult.getMethod();
Method testMethod = testNGMethod.getMethod();

and

final Map<String, String> parameters = 
(((TestRunner) context).getTest()).getParameters();

Comparison of the version 6.3 interface with version 7.5 interface

Comparison of the version 6.3 interface

with version 7.5 interface

TODO

on Linux develpment machine, seem to not be able to launch google tests. After authenticaling o Windows machine, issue disappears

[ERROR] org.testng.TestNGException:
[ERROR] Cannot find class in classpath: com.github.sergueik.testng.ExcelProviderTest

Author

Serguei Kouzmine