JUnit4 多线程执行测试用例

来源:互联网 发布:软件过程的概念 编辑:程序博客网 时间:2024/04/30 01:44

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


0 0
原创粉丝点击