Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

uniquely identifying each test invocation in testng

Tags:

java

testng

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?

like image 402
Jay Avatar asked Oct 27 '15 09:10

Jay


People also ask

What is invocation in TestNG?

TestNG supports multi-invocation of a test method, i.e., a @Test method can be invoked multiple times sequentially or in parallel. If we want to run single @Test 10 times at a single thread, then invocationCount can be used. To invoke a method multiple times, the keyword invocationCount = <int> is required.

What is @parameter annotation in TestNG?

Parameters Annotation in TestNG is a method used to pass values to the test methods as arguments using . xml file. Users may be required to pass the values to the test methods during run time. The @Parameters annotation method can be used in any method having @Test, @Before, @After or @Factory annotation.

What is the use of @listener annotation in TestNG?

@Listeners annotation defines listeners on a test class. @Listeners annotated method listens to certain events and keep track of test execution while performing some action at every stage of test execution.

What is the use of Testnglisteners?

TestNG Listener is a handy feature for testing and QA teams to easily automate testing projects and easily identify how each is performing. For a failed test or a skipped test, you can take the screenshot and identify why the test failed or skipped.


3 Answers

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
===============================================
like image 158
Francisco Hernandez Avatar answered Sep 28 '22 11:09

Francisco Hernandez


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.

like image 22
mzlobecki Avatar answered Sep 28 '22 09:09

mzlobecki


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.

like image 28
Bohemian Avatar answered Sep 28 '22 11:09

Bohemian