Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making each test method run in its own instance of a test class with TestNG?

So I thought the following code would run fine in TestNG, although it doesn't:

public class Tests {
    int i = 0;

    @Test
    public void testA() {
        Assert.assertEquals(0, i);
        ++i;
    }

    @Test
    public void testB() {
        Assert.assertEquals(0, i);
        ++i;
    }
}

Is there a way to make TestNG fire up a new Tests class for each test method?

like image 727
devoured elysium Avatar asked Apr 30 '11 14:04

devoured elysium


People also ask

How do you run TestNG tests sequentially?

parallel="methods": TestNG will run all your test methods in separate threads. Dependent methods will also run in separate threads but they will respect the order that you specified. parallel="tests": TestNG will run all the methods in the same tag in the same thread, but each tag will be in a separate thread.

Can you use multiple test annotations in a single TestNG file?

We can run multiple test cases using TestNG test suite in Selenium webdriver. To execute test cases simultaneously, we have to enable parallel execution in TestNG. A TestNG execution is driven by the TestNG xml file. To trigger parallel execution we have to use the attributes – parallel and thread-count.


2 Answers

The common solution is to use an @BeforeMethod method to setup test state,

@BeforeMethod
public void setup() {
   i = 0; 
}
like image 162
sbridges Avatar answered Oct 09 '22 10:10

sbridges


By far the most common solution to this issue I have found is to use ThreadLocal’s and just deal with the fact that you only have one instance of each Test Class. This deals with all the questions on how to deal with parallel/threaded tests. This works, but is a bit ugly.

private ThreadLocal<Integer> i = new ThreadLocal<>();

@BeforeMethod
public void setup() {
    i.set(0);
}

@Test
public void testA() {
    Integer i1 = i.get();
    Assert.assertEquals(0, i.get().intValue());
    i.set(i1 + 1);
}

@Test
public void testB() {
    Integer i1 = i.get();
    Assert.assertEquals(0, i.get().intValue());
    i.set(i1 + 1);
}

Now back to the root of your question, new instances for each method. I’ve been researching for a few weeks similar topics, and I have identified this is the number one issue I was personally having with TestNG. It has literally driven me crazy.

If I was to ignore the fact that your tests had a bunch of complexities, you could potentially hack together a work around to meet the requirements you listed.

A TestNG @Factory Factory allows you to create new instances of your test classes.

@Factory
public Object[] factory(){
    return new Object[]{new Tests(), new Tests()};
}

I’ve now created two Tests instances, to be ran by testNG

Then the issue is your tests still fail, because it will try to run all test methods on your test classes. In order to hack around this you could implement a IMethodInterceptor, and hack together a solution to enforce that each Tests instance only run one method. Maintain a list of methods, and go through them one at a time.

Here is a brute example I hacked together.

public class TestFactory implements IMethodInterceptor {
    private List<String> methodsToRun = new ArrayList<>();
    private List<Object> testInstances = new ArrayList<>();

    @Factory
    public Object[] factory(){
        return new Object[]{new Tests(), new Tests()};
    }

    @Override
    public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
        ArrayList<IMethodInstance> tempList = new ArrayList<>();
        for(IMethodInstance i: methods){
            if(testInstances.contains(i.getInstance())){
                continue;
            }
            String mName = i.getMethod().getConstructorOrMethod().getName();
            if(!methodsToRun.contains(mName)){
                tempList.add(i);
                methodsToRun.add(mName);
                testInstances.add(i.getInstance());
            }
        }
        return tempList;
    }
}

Then add your listener to the top of your Tests class

@Listeners(TestFactory.class)

You can improve this by dynamically creating new instances of the tests in the factory. Also breaking the listener out into it's own file and numerous other improvements, but you get the gist.

Maybe a crazy solution like the above will work for you or someone else.

like image 30
Copy and Paste Avatar answered Oct 09 '22 09:10

Copy and Paste