JUnit4 多線程執行測試用例

轉載地址: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的全局變量,全改寫到測試方法內定義,變成局部變量。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章