并行执行测试时获取顺序日志

Getting sequential logs while executing tests in parallel

我们一直在使用 testngjava 来对我们的代码执行集成测试。我们为测试执行实现了 listener 如下:-

public class TestExecutionListener implements IInvokedMethodListener {

    @Override
    public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("Testing : " + iInvokedMethod.getTestMethod().getMethodName());
    }

    @Override
    public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("Successfully Tested : " + iInvokedMethod.getTestMethod().getMethodName());
    }
}

我们的testng.xml定义为:-

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="TestSuite" verbose="1" parallel="classes" thread-count="10">
    <listeners>
        <listener class-name="core.TestExecutionListener"/>
    </listeners>

    <test name="IntegrationTests">
        <classes>
            <class name="test.SomeTest1"/>
            <class name="test.SomeTest2"/>
            <class name="test.SomeTest3"/>
            <class name="test.SomeTest4"/>
            ... There are more than 20 classes
        </classes>
    </test>
</suite>

当我们执行测试时,我们得到的输出如下:

Testing : SomeTest1Method1
Testing : SomeTest2Method2
Testing : SomeTest4Method5
Successfully Tested : SomeTest2Method2
Successfully Tested : SomeTest4Method5

而我们期望的输出是:-

Testing : SomeTest1Method1
Successfully Tested : SomeTest1Method1
Testing : SomeTest2Method2
Successfully Tested : SomeTest2Method2
Testing : SomeTest4Method5
Successfully Tested : SomeTest4Method5

猜测这是因为 xml 中的 parallel="classes" 属性,因为将其更改为 false 可提供所需的输出。但是很明显,与并行执行相比,更改后的执行会消耗更多的时间。

有没有办法并行运行这些测试但仍然按顺序得到这个输出?

首先,我建议您使用 log4j 或类似的记录器工具,它们优雅、模块化并提供许多灵活的选项。这也为以多种方式设计和处理日志记录机制奠定了基础。

如果您有业务需求,需要您分别跟踪每个线程的日志,您应该考虑将这些日志流式传输到不同的文件。 Log4j 类工具确实支持写入多个文件的需求。你可以google搜索,你会得到很多信息。

当您启用并行时,正如您所注意到的,日志中的 beforeInvocationafterInvocation 之间会有一些时间,并且时间差异因测试而异,因此输出交错。

如果您想要的是彼此相邻的开始和结束消息,那么您基本上是在丢弃时间因素,因此可以简单地将您的 beforeInvocation 消息添加到 afterInvocation 方法中如下:

public class TestExecutionListener implements IInvokedMethodListener {

    @Override
    public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
    }

    @Override
    public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("Testing : " + iInvokedMethod.getTestMethod().getMethodName());
        System.out.println("Successfully Tested : " + iInvokedMethod.getTestMethod().getMethodName());
    }
}

IMO 这是根据您的规格执行此操作的唯一方法。但是,如果在测试期间必须收集其他信息,那么也许您可以在 TestExecutionListener 中缓冲一些日志,例如:

public class TestExecutionListener implements IInvokedMethodListener {
    private Map<Integer, Deque<String>> logsMap = new HashMap<Integer, Deque<String>>();

    public void log(IInvokedMethod iInvokedMethod, String log) {
        if(!logsMap.containsKey(iInvokedMethod.getId())) {
            logsMap.put(iInvokedMethod.getId(), new ArrayDeque<String>());
        }

        logsMap.get(iInvokedMethod.getId()).add(log);
    }

    @Override
    public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        log(iInvokedMethod, "Testing : " + iInvokedMethod.getTestMethod().getMethodName());
    }

    @Override
    public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        log(iInvokedMethod, "Successfully Tested : " + iInvokedMethod.getTestMethod().getMethodName());

        Deque<String> logs = logsMap.get(iInvokedMethod.getId());
        while(!logs.isEmpty()) {
            System.out.println(logs.poll());
        }
    }
}