转载地址: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的全局变量,全改写到测试方法内定义,变成局部变量。