Making each test method run in its own instance of

2020-02-28 10:53发布

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?

2条回答
Fickle 薄情
2楼-- · 2020-02-28 11:32

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.

查看更多
姐就是有狂的资本
3楼-- · 2020-02-28 11:41

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

@BeforeMethod
public void setup() {
   i = 0; 
}
查看更多
登录 后发表回答