轉載地址:http://testerhome.com/topics/2502
前言:
之前發過類似的文章,現重新調整了部分格式,部分內容稍作調整和添加,便於閱讀。
評論中,有人說直接使用TestNG,就可以實現多線程,是的,但是方式不一樣;我們是按照自己的需求對JUnit4自定義多線程Runner,直接在某個類加上相應的註解即可,運行該類就行,支持類和方法級別;TestNG只在方法上有註解 @Test(threadPoolSize
= m, invocationCount = n, timeOut = i)實現了對這個方法進行多線程重複跑,threadPoolSize多少個線程執行該方法,invocationCount被執行次數,timeOut每次執行該方法的超時時間,這僅是用多線程重複執行這一個方法,而不是類下面的所有方法同時併發執行,並不是所謂的方法級別併發;TestNG是在xml指定併發的類,方法,組件,具體參照TestNG
Executing Parallel Tests Example。
這裏不討論TestNG與JUnit4誰好誰壞,JUnit 4 vs TestNG,只要能滿足自己的業務需要即可。
本文僅針對JUnit4進行二次開發。
JUnit4本身是支持多線程,但沒有提供多線程的註解;本文將介紹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) ,重新定義了調度順序,定義了一個線程池 private final ExecutorService fService = Executors.newCachedThreadPool()來多線程執行,運行結束後finished(),關閉線程池fService.shutdown(),並返回該runner。
其中ParallelComputer類重寫了父類 Computer的getSuite()和getRunner:
@Override
public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes)
throws InitializationError {
Runner suite = super.getSuite(builder, classes);
return this.classes ? parallelize(suite) : suite;
}
@Override
protected Runner getRunner(RunnerBuilder builder, Class<?> testClass)
throws Throwable {
Runner runner = super.getRunner(builder, testClass);
return methods ? parallelize(runner) : runner;
}
getSuite()和getRunner()根據ParallelComputer類的全局final變量classes和methods的值去決定是否多線程執行;
classes爲true時,併發以類爲維度,如下:return
this.classes ? parallelize(suite) : suite;
methods爲true時,併發以方法爲維度,如下:return
methods ? parallelize(runner) : runner;
ParallelComputer類提供了帶參的構造函數:public
ParallelComputer(boolean classes, boolean methods)
可以在類初始化時,直接定義多線程執行(不同維度)的對象。
JUnitCore類中的方法runClasses():public
static Result runClasses(Computer computer,Class<?>... classes)
,可以在main()函數裏直接運行測試用例,參數Computer是ParallelComputer的父類,可以直接new ParallelComputer(boolean classes, boolean methods)對象作爲第一個形參。
實例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.
*
*/
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);
}
}
});
}
}
新建一個聚合的IntegrationBeijingOneTests.java文件:
@RunWith(ConcurrentSuite.class)
@ClassnameFilters({"com.weibo.cases.xuelian.*Test", "!.*RemindTest","com.weibo.cases.maincase.*Xuelian"})
@Concurrent
public interface IntegrationBeijingOneTests {
}
再建一個suite文件,XuelianTestSuite.java:
package com.weibo.cases.suite;
import org.junit.experimental.categories.Categories;
import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Categories.class)
@SuiteClasses( IntegrationBeijingOneTests.class )
public class XuelianTestSuite {
}
直接運行XuelianTestSuite.java即可,執行過程如下:
寫在最後:
設計測試用例時需考慮線程安全。
建議(本組內用例):
1.賬號的使用,同一個測試類中每個測試方法之間需使用不同測試賬號(之前未考慮併發,串行執行時方法間使用同樣賬號,沒有影響),咱們組V4的用例共1516個,假設每個用例使用3個賬號,則同時執行用例時,則需4548個賬號,現庫裏有1617個賬號,可能需要增加用戶(空間換時間); 當然也可以控制併發執行測試方法的數量,來減少用戶的使用,比如可以指定同時5個(可調)測試方法併發執行,當然,執行時間上就會相應的增加。
2.非final的全局變量,全改寫到測試方法內定義,變成局部變量。