10
votes

Ok, so I have a class like this

public class Calculator {

    @Test(dataProvider = "dp")
    public void add(int a, int b) {
        System.out.println("Invoked add: a, b" + a + "," + b);
    }

    @DataProvider(name = "dp")
    public Object[][] createData(ITestContext ctx) {
        return new Object[][] { new Object[] { 1, 2 }, new Object[] { 2, 3 } };
    }

When, the test runs, it would run add method twice. I want to track each invocation of add uniquely based on its input. So say, add is invoked with 1,2 as input then that's a unique invocation. If it fails, I want to store this information to a database with an invocation id.

How do I achieve this using testng? All of the listeners (methodinvocationlistener etc), do not seem to provide context that uniquely identifies a method run. Yes, they do let you see the parameters, but I cannot track individual parameters. So, do I somehow inject my own unique parameter into the result object and track it from there?

UPDATE

I am adding improved code, to help understand the context better. This is my testng.xml

<suite name="Default Suite">
  <test name="test">
    <classes>
      <class name="com.test.testng.Calculator">
        <methods>
          <include name="add">
            <parameter name="data-id" value="1"/>
          </include> <!-- add -->
          <include name="add">
            <parameter name="data-id" value="2"/>
          </include> <!-- add -->
          <include name="subtract">
            <parameter name="data-id" value="3"/>
          </include> <!-- subtract -->
        </methods>
      </class> <!-- com.test.testng.Calculator -->
    </classes>
  </test> <!-- test -->
</suite> <!-- Default Suite -->

I have two invocations of add and one invocation of subtract. Here's my data provider

public class Calculator {

    @Test(dataProvider = "dp")
    public void add(int first, int second) {
        System.out.println("invoked add");
    }

    @Test(dataProvider = "dp")
    public void subtract(int first, int second) {
        System.out.println("invoked subtract");
    }

    @DataProvider(name = "dp")
    public Object[][] createData(Method m, ITestContext ctx) {    
        Object[][] data = new Object[][] { new Object[] { 1, 2 }, new Object[] { 2, 3 }, new Object[] { 3, 4 } };
        for (XmlClass test : ctx.getCurrentXmlTest().getXmlClasses()) {
            for (XmlInclude method : test.getIncludedMethods()) {
                if (method.getName().equals(m.getName()))
                int key = Integer.parseInt(method.getAllParameters().get("data-id"));
                return new Object[][] { data[key - 1] };
            }
        }
        return null ;
    }

}

I expected, add to run twice, once with 1,2 as input and another time with 2,3 as input. Similarly, subtract with 3,4 as input. But, what I saw was this -

[SuiteRunner] Created 1 TestRunners
[TestRunner] Running test test on 1  classes,  included groups:[] excluded groups:[]
===== Test class
com.test.testng.Calculator
    @Test Calculator.add(int, int)[pri:0, instance:com.test.testng.Calculator@39a054a5]
    @Test Calculator.subtract(int, int)[pri:0, instance:com.test.testng.Calculator@39a054a5]
======
method.getAllParamas(){data-id=1}

[Invoker 665576141] Invoking com.test.testng.Calculator.add
invoked

[Invoker 665576141] Invoking com.test.testng.Calculator.subtract
subtract
===== Invoked methods
    Calculator.add(int, int)[pri:0, instance:com.test.testng.Calculator@39a054a5]1 2  966808741
    Calculator.subtract(int, int)[pri:0, instance:com.test.testng.Calculator@39a054a5]1 2  966808741
=====

I need to provide data to each method based on the special parameter that I am going to send from testng xml. How do I achieve this?

3
What is the use-case here? Why would you want/need to persist state beyond the test-case run?Oliver Charlesworth
the use case is - Let's this 'Calculator' class has 10 methods. I would like the user to select which methods he wants to test from a UI, then for each method he marked for testing, he would upload data related to each method through a spreadsheet. This spreadsheet data is persisted to the database and a key is generated. At runtime, when I generate a testng xml, it will contain these keys and the DataProvider will create input after reading the persisted data from database. Now, as you can see, I have different invocations, for tests, all of them dynamically created. I need to track them.Jay
Ok. In that case can't you store a UID/key per entry in the database, read that as part of the DataProvider, and pass the UID as a parameter to the test method?Oliver Charlesworth
@OliverCharlesworth Can you please add example code to what your are suggesting.Jay
@OliverCharlesworth I know what you are trying to say. At the DataProvider level, I can pass the UID, but again, this will have to be made part of my datamodel. For instance, let's say the add() method accepts add(Tuple tuple) as parameter instead of two ints. Then I need to now design this Tuple class with int id, int a, int b where id represents the unique id. I will have to do this all my POJOs. I was wondering if there is a simpler way.Jay

3 Answers

1
votes

To achieve this you can define diferent tests cases in testng.xml, like this:

<suite name="Default Suite">
  <test name="test">
    <classes>
      <class name="com.test.testng.Calculator">
        <methods>
          <include name="add">
            <parameter name="data-id" value="1"/>
          </include> <!-- add -->
          <include name="subtract">
            <parameter name="data-id" value="3"/>
          </include> <!-- subtract -->
        </methods>
      </class> <!-- com.test.testng.Calculator -->
    </classes>
  </test> <!-- test -->
  <test name="test2">
    <classes>
      <class name="com.test.testng.Calculator">
        <methods>
          <include name="add">
            <parameter name="data-id" value="2"/>
          </include> <!-- add -->
        </methods>
      </class> <!-- com.test.testng.Calculator -->
    </classes>
  </test> <!-- test -->
</suite> <!-- Default Suite -->

I add a log in provider method:

package com.test.testng;

import java.lang.reflect.Method;

import org.testng.ITestContext;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlInclude;

public class Calculator {

    @Test(dataProvider = "dp")
    public void add(int first, int second) {
        System.out.println("invoked add");
    }

    @Test(dataProvider = "dp")
    public void subtract(int first, int second) {
        System.out.println("invoked subtract");
    }

    @DataProvider(name = "dp")
    public Object[][] createData(Method m, ITestContext ctx) {    
        Object[][] data = new Object[][] { new Object[] { 1, 2 }, new Object[] { 2, 3 }, new Object[] { 3, 4 } };
        for (XmlClass test : ctx.getCurrentXmlTest().getXmlClasses()) {
            for (XmlInclude method : test.getIncludedMethods()) {

                if (method.getName().equals(m.getName())) {
                    int key = Integer.parseInt(method.getAllParameters().get("data-id"));
                    System.out.println("Running method " + m.getName() + "  with data-id: " + key);
                    return new Object[][] { data[key - 1] };
                }


            }
        }
        return null ;
    }

}

Running this xml as testng suite (with eclipse plugin) shows this:

[TestNG] Running:
  /Users/fhernandez/Documents/workspaceTest/testNg-test/src/test/resources/testng.xml

Running method add  with data-id: 1
invoked add
Running method subtract  with data-id: 3
invoked subtract
Running method add  with data-id: 2
invoked add

===============================================
Default Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

If I understand well your requirements, with this you can achieve it.

Another way for achieve this would be put an aspect or a proxy before test classes and do inspection of method invocation, implementing in this aspect/proxy your requirements.

UPDATE

I add a Listener to Calculator with

@Listeners(Listener.class)
public class Calculator

The listener looks like the following

    package com.test.testng;

import java.util.Arrays;

import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlInclude;

public class Listener implements IInvokedMethodListener {

    public void afterInvocation(IInvokedMethod method, ITestResult itr) {
        // TODO implements
    }

    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {

        // Parameters value
        System.out.println("Parameters invocation value for method " + method.getTestMethod().getMethodName());
        Arrays.asList(testResult.getParameters()).stream().forEach(System.out::println);

        // get data-id
        for (XmlClass test : testResult.getTestContext().getCurrentXmlTest().getXmlClasses()) {
            for (XmlInclude met : test.getIncludedMethods()) {

                if (met.getName().equals(method.getTestMethod().getMethodName())) {
                    int key = Integer.parseInt(met.getAllParameters().get("data-id"));
                    System.out.println("listener: Running method " + method.getTestMethod().getMethodName() + "  with data-id: " + key);
                }        
            }
        }
    }
}

The code in beforeInvocation show the parameter invocation values and data-id, this is the output

    [TestNG] Running:
  /Users/fhernandez/Documents/workspaceTest/testNg-test/src/test/resources/testng.xml

Running method add  with data-id: 1
Parameters invocation value for method add
1
2
listener: Running method add  with data-id: 1
invoked add
Running method subtract  with data-id: 3
Parameters invocation value for method subtract
3
4
listener: Running method subtract  with data-id: 3
invoked subtract
Running method add  with data-id: 2
Parameters invocation value for method add
2
3
listener: Running method add  with data-id: 2
invoked add

===============================================
Default Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================
1
votes

First argument in [][] is number of testing runs. Second is number of parameters. I.e.
public Object[][] createData(ITestContext ctx) { return new Object[][] { new Object[] { 0,2 }, new Object[] { 0,3} }; }


Will run one test with two parameters 2,3.

1
votes

Unit tests are for the developer to check his/her own code, and no one else.

Comments to the question indicate "user specified parameters/tests" are in the context, which puts it in the system test realm.

Don't use a unit testing framework to execute system tests.

An important point is that in your proposed environment it is possible for something external to the code (a user adding new test parameters) to break the build. This is bad. Very bad. If new functionality is required, the developer will enhance the code and add new tests to cover it.

Have a look at the BDD framework JBehave, which seems a better fit to your need.