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

胡刚 · 2015年02月27日 · 最后由 Teambition 回复于 2016年06月13日 · 50 次阅读

本文未粘贴图片,完整版地址: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” 键查看更多 帮助

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册