自动化测试工具 JUnit4---专题之多线程执行用例

胡刚 · February 27, 2015 · Last by Teambition replied at June 13, 2016 · 1721 hits

本文未粘贴图片,完整版地址:http://blog.csdn.net/neven7/article/details/43951285

JUnit4本身是支持多线程,但没有提供多线程的Runner;本文将介绍JUnit4内部自身的多线程实现,自定义多线程Runner和聚合测试类时多线程Runner。
(一)JUnit4自身的多线程

JUnit4提供了ParallerComputer类来实现多线程执行用例。
java.lang.Object
extended by org.junit.runner.Computer
extended by org.junit.experimental.ParallelComputer
源码如下:

001    package org.junit.experimental;
002
003 import java.util.concurrent.ExecutorService;
004 import java.util.concurrent.Executors;
005 import java.util.concurrent.TimeUnit;
006
007 import org.junit.runner.Computer;
008 import org.junit.runner.Runner;
009 import org.junit.runners.ParentRunner;
010 import org.junit.runners.model.InitializationError;
011 import org.junit.runners.model.RunnerBuilder;
012 import org.junit.runners.model.RunnerScheduler;
013
014 public class ParallelComputer extends Computer {
015 private final boolean classes;
016
017 private final boolean methods;
018
019 public ParallelComputer(boolean classes, boolean methods) {
020 this.classes = classes;
021 this.methods = methods;
022 }
023
024 public static Computer classes() {
025 return new ParallelComputer(true, false);
026 }
027
028 public static Computer methods() {
029 return new ParallelComputer(false, true);
030 }
031
032 private static Runner parallelize(Runner runner) {
033 if (runner instanceof ParentRunner) {
034 ((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() {
035 private final ExecutorService fService = Executors.newCachedThreadPool();
036
037 public void schedule(Runnable childStatement) {
038 fService.submit(childStatement);
039 }
040
041 public void finished() {
042 try {
043 fService.shutdown();
044 fService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
045 } catch (InterruptedException e) {
046 e.printStackTrace(System.err);
047 }
048 }
049 });
050 }
051 return runner;
052 }
053
054 @Override
055 public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes)
056 throws InitializationError {
057 Runner suite = super.getSuite(builder, classes);
058 return this.classes ? parallelize(suite) : suite;
059 }
060
061 @Override
062 protected Runner getRunner(RunnerBuilder builder, Class<?> testClass)
063 throws Throwable {
064 Runner runner = super.getRunner(builder, testClass);
065 return methods ? parallelize(runner) : runner;
066 }
067 }

ParallelComputer类中parallelize(Runner runner)方法重写了
ParentRunner类的方法runner.setScheduler(RunnerSchedulerscheduler) ,重新定义了调度顺序,使用线程池fService.submit(childStatement)多线程执行;运行结束后finished(),关闭线程池fService.shutdown();并返回该runner。
ParallelComputer类重写了父类 Computer的

//  Create a suite for classes, building Runners with builder.
public Runner getSuite(RunnerBuilder builder,
Class<?>[] classes)
throws InitializationError

// Create a single-class runner for testClass, using builder
protected Runner getRunner(RunnerBuilder builder,
Class<?> testClass)
throws Throwable

根据成员变量,来判断执行的维度;fClasses为true时,并发以类为维度;fMethods为true时,并发以方法为维度。

private final boolean fClasses;

private final boolean fMethods;

提供了带参构造函数,直接赋值。
public ParallelComputer(boolean classes, boolean methods)
JUnitCore提供了
public static Result runClasses(Computer computer,
Class<?>... classes)方法,直接运行测试类,参数computer可以自定义ParallelComputer对象来并发执行。
实例1:

public class A {

@Test
public void a() {
assertThat(3, is(1));
}

@Test
public void b() {
assertThat(3, not(1));
}

}
public class B {

@Test
public void c() {
assertThat(3, greaterThan(1));
}

@Test
public void d() {
assertThat(3, lessThan(1));
}

}
public class ParallelTest {

public static void main(String[] args) {
Class[] cls = { A.class, B.class };

Result rt;

// 并发以类为维度
// rt = JUnitCore.runClasses(ParallelComputer.classes(), cls);

// 并发以方法为维度
// rt = JUnitCore.runClasses(ParallelComputer.methods(), cls);

// 并发以类和方法为维度
rt = JUnitCore.runClasses(new ParallelComputer(true, true), cls);

System.out.println(rt.getRunCount() + " " + rt.getFailures() + " " + rt.getRunTime());
}

}

// A,B两个类并发执行,但类的方法还是串行执行;
JUnitCore.runClasses(ParallelComputer.classes(), cls);
// A,B两个类串行执行,但类的方法并发执行
JUnitCore.runClasses(ParallelComputer.methods(), cls);
// A,B两个类并发执行,其方法也并发执行
JUnitCore.runClasses(new ParallelComputer(true, true), cls);

(二)自定义多线程Runner

package com.weibo.concurrent;

import java.util.concurrent.atomic.AtomicInteger;

import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

/**
* Runs all tests in parallel and waits for them to complete.
* hugang
*/

public class MultiThreadedRunner extends BlockJUnit4ClassRunner {

private AtomicInteger numThreads;

public static int maxThreads = 10;

public MultiThreadedRunner (Class<?> klass) throws InitializationError {
super (klass);
numThreads = new AtomicInteger(0);
}

// Runs the test corresponding to child,which can be assumed to be an element of the list returned by getChildren()
@Override
protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
while (numThreads.get() > maxThreads) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.err.println ("Interrupted: " + method.getName());
e.printStackTrace();
return; // The user may have interrupted us; this won't happen normally
}
}
numThreads.incrementAndGet();
// 用线程执行父类runChild(method, notifier)
new Thread (new Test(method, notifier)).start();
}

// childrenInvoker() call runChild(Object, RunNotifier) on each object returned by getChildren()
// evaluate() run the action, 调用父类BlockJUnit4ClassRunner的evaluate()
@Override
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
MultiThreadedRunner.super.childrenInvoker(notifier).evaluate();
// wait for all child threads (tests) to complete
while (numThreads.get() > 0) {
Thread.sleep(1000);
}
}
};
}

class Test implements Runnable {
private final FrameworkMethod method;
private final RunNotifier notifier;

public Test (final FrameworkMethod method, final RunNotifier notifier) {
this.method = method;
this.notifier = notifier;
}

@Override
public void run () {
System.err.println (method.getName());
MultiThreadedRunner.super.runChild(method, notifier);
numThreads.decrementAndGet();
}
}

}

只要在测试类前,加上@RunWith(MultiThreadRunner.class),就可以并发的执行用例。

(三)聚合测试类时多线程Runner

有时我们需要聚合某一类型的测试类,如果使用@RunWith(Suite.class) @SuiteClasses({A.class,B.class}),当类较多时,需要一一列举,效率不高;可以使用ClasspathSuite,支持过滤,将某一类的测试类聚合,官方文档。

package com.weibo.concurrent;

import org.junit.experimental.categories.Categories;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.runner.Runner;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.RunnerScheduler;

import com.weibo.common.MbLogger;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @author hugang
*
**/

public final class ConcurrentSuite extends ClasspathSuite {

public static Runner MulThread(Runner runner) {
if (runner instanceof ParentRunner) {
// setScheduler(RunnerScheduler scheduler):Sets a scheduler that
// determines the order and parallelization of children
// RunnerScheduler:Represents a strategy for scheduling when
// individual test methods should be run (in serial or parallel)
((ParentRunner) runner).setScheduler(new RunnerScheduler() {
private final ExecutorService fService = Executors
.newCachedThreadPool();

// private final ExecutorService fService =
// Executors.newFixedThreadPool(10);

// Schedule a child statement to run
public void schedule(Runnable childStatement) {
this.fService.submit(childStatement);
}

// Override to implement any behavior that must occur after all
// children have been scheduled
public void finished() {
try {
this.fService.shutdown();
this.fService.awaitTermination(9223372036854775807L,
TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
});
}
return runner;
}

public ConcurrentSuite(final Class<?> klass) throws InitializationError {
// 调用父类ClasspathSuite构造函数
// AllDefaultPossibilitiesBuilder根据不同的测试类定义(@RunWith的信息)返回Runner,使用职责链模式
super(klass, new AllDefaultPossibilitiesBuilder(true) {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
List<RunnerBuilder> builders = Arrays
.asList(new RunnerBuilder[] { ignoredBuilder(),
annotatedBuilder(), suiteMethodBuilder(),
junit3Builder(), junit4Builder() });
for (RunnerBuilder each : builders) {
// 根据不同的测试类定义(@RunWith的信息)返回Runner
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null)
// 方法级别,多线程执行
return MulThread(runner);
}
return null;
}
});

// 类级别,多线程执行
setScheduler(new RunnerScheduler() {
private final ExecutorService fService = Executors
.newCachedThreadPool();

@Override
public void schedule(Runnable paramRunnable) {
// TODO Auto-generated method stub
fService.submit(paramRunnable);
}

@Override
public void finished() {
// TODO Auto-generated method stub
try {
fService.shutdown();
fService.awaitTermination(Long.MAX_VALUE,
TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}

});
}

}

如下使用:

@RunWith(ConcurrentSuite.class)
@ClassnameFilters({"com.weibo.cases.xuelian.*Test", "!.*RemindTest","com.weibo.cases.maincase.*Xuelian"})
@Concurrent
public interface IntegrationBeijingOneTests {

}

写在最后:
设计测试方法时需考虑线程安全,对临界资源加锁。
建议(本组内用例):
1.账号的使用,同一个测试类中每个测试方法之间需使用不同测试账号(之前未考虑并发,串行执行时方法间使用同样账号,没有影响),咱们组V4的用例共1516个,假设每个用例使用3个账号,则同时执行用例时,则需4548个账号,现库里有1617个账号,可能需要增加用户(空间换时间); 当然也可以控制并发执行测试方法的数量,来减少用户的使用,比如可以指定同时5个(可调)测试方法并发执行,当然,执行时间上就会相应的增加。
2.非final的全局变量,全改写到测试方法内定义,变成局部变量。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 7 条回复 时间 点赞

代码请贴代码并使用代码块!
日志请贴日志并使用代码块!
提问,指明问题信息,请像一个职业测试一样报问题
支持 Markdown 格式, 粗体删除线单行代码
支持表情,见 Emoji cheat sheet
按“M”键查看更多 帮助。

代码请贴代码并使用代码块!
日志请贴日志并使用代码块!
提问,指明问题信息,请像一个职业测试一样报问题
支持 Markdown 格式, 粗体、删除线、单行代码
支持表情,见 Emoji cheat sheet
按“M”键查看更多 帮助。

代码请贴代码并使用代码块!
日志请贴日志并使用代码块!
提问,指明问题信息,请像一个职业测试一样报问题
支持 Markdown 格式, 粗体、删除线、单行代码
支持表情,见 Emoji cheat sheet
按“M”键查看更多 帮助

你们要吓住新同学啦. 欢迎新同学发帖

原帖可以加精。这个还不行。

给刚哥点赞,嘿嘿

代码请贴代码并使用代码块!
日志请贴日志并使用代码块!
提问,指明问题信息,请像一个职业测试一样报问题
支持 Markdown 格式, 粗体、删除线、单行代码
支持表情,见 Emoji cheat sheet
按“M”键查看更多 帮助

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up