surefire 拉起testng单元测试类的源码流程阅读(二)

本文分析了基于Surefire 2.19.1版本的单元测试执行流程及异常情况,详细解析了Surefire如何启动TestNG进行测试,并深入探讨了测试执行过程中遇到的非法类格式异常。

这里是基于surefire 2.19.1版本分析的。

还是根据surefire 拉起单元测试执行报错的日志展示的执行过程分析

java.lang.instrument.IllegalClassFormatException: Error while instrumenting class com/suning/imp/scheduler/ActivityDoneMonitorSchedule.
	at org.jacoco.agent.rt.internal_6da5971.CoverageTransformer.transform(CoverageTransformer.java:93)
	at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
	at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at org.springframework.util.ClassUtils.forName(ClassUtils.java:258)
	at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:417)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1283)
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1254)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:576)
	at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1331)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:341)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:420)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:617)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:451)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:106)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:57)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:100)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:248)
	at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:124)
	at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:148)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:313)
	at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextPrepareTestInstance(AbstractTestNGSpringContextTests.java:130)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
	at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:564)
	at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:213)
	at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:138)
	at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:175)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:107)
	at org.testng.TestRunner.privateRun(TestRunner.java:767)
	at org.testng.TestRunner.run(TestRunner.java:617)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
	at org.testng.SuiteRunner.run(SuiteRunner.java:254)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
	at org.testng.TestNG.run(TestNG.java:1057)
	at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:281)
	at org.apache.maven.surefire.testng.TestNGXmlTestSuite.execute(TestNGXmlTestSuite.java:75)
	at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:121)
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:290)
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:242)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:121)
Caused by: java.io.IOException: Error while instrumenting class com/suning/imp/scheduler/ActivityDoneMonitorSchedule.
	at org.jacoco.agent.rt.internal_6da5971.core.instr.Instrumenter.instrumentError(Instrumenter.java:160)
	at org.jacoco.agent.rt.internal_6da5971.core.instr.Instrumenter.instrument(Instrumenter.java:111)
	at org.jacoco.agent.rt.internal_6da5971.CoverageTransformer.transform(CoverageTransformer.java:91)
	... 62 more
Caused by: java.lang.IllegalStateException: Class com/suning/imp/scheduler/ActivityDoneMonitorSchedule is already instrumented.
	at org.jacoco.agent.rt.internal_6da5971.core.internal.instr.InstrSupport.assertNotInstrumented(InstrSupport.java:89)
	at org.jacoco.agent.rt.internal_6da5971.core.internal.instr.ClassInstrumenter.visitField(ClassInstrumenter.java:55)
	at org.jacoco.agent.rt.internal_6da5971.asm.ClassVisitor.visitField(ClassVisitor.java:272)
	at org.jacoco.agent.rt.internal_6da5971.asm.ClassReader.readField(ClassReader.java:768)
	at org.jacoco.agent.rt.internal_6da5971.asm.ClassReader.accept(ClassReader.java:689)
	at org.jacoco.agent.rt.internal_6da5971.asm.ClassReader.accept(ClassReader.java:506)
	at org.jacoco.agent.rt.internal_6da5971.core.instr.Instrumenter.instrument(Instrumenter.java:84)
	at org.jacoco.agent.rt.internal_6da5971.core.instr.Instrumenter.instrument(Instrumenter.java:108)
	... 63 more

-> SuiteRunnerWorker.run -> runSuites

-> SuiteRunner.run -> privateRun -> invokeTestMethods 

->testng.internal.TestMethodWorker.run -> invokeTestMethods

->testng.internal.Invoker.invokeTestMethods -> invokeMethod

->testng.internal.MethodInvocationHelper.invokeMethod

然后surefire 通过调用 booter.SurefireBooter.main

(surefire-booter.2.19.1.jar) -> booter.ForkedBooter.runSuitesInProcess

-> booter.ForkedBooter.invokeProviderInSameClassLoader

-> surefire.testng.TestNGProvider.invoke (surefire-testng-2.19.1)
-> surefire.testng.TestNGXmlTestSuite.execute
-> surefire.testng.TestNGExecutor.run

-> TestNG.run(testng-6.9.12)

surefireBooter类代码:

public final class ForkedBooter
{
    private static final long SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30;
    private static final long PING_TIMEOUT_IN_SECONDS = 20;

    private static final ScheduledExecutorService JVM_TERMINATOR = createJvmTerminator();

    /**
     * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
     * then calls the Surefire class' run method. <p/> The system exit code will be 1 if an exception is thrown.
     *
     * @param args Commandline arguments
     */
    public static void main( String... args )
    {
        final CommandReader reader = startupMasterProcessReader();
        final ScheduledFuture<?> pingScheduler = listenToShutdownCommands( reader );
        final PrintStream originalOut = System.out;
        try
        {
            if ( args.length > 1 )
            {
                SystemPropertyManager.setSystemProperties( new File( args[1] ) );
            }

            File surefirePropertiesFile = new File( args[0] );
            InputStream stream = surefirePropertiesFile.exists() ? new FileInputStream( surefirePropertiesFile ) : null;
            BooterDeserializer booterDeserializer = new BooterDeserializer( stream );
            ProviderConfiguration providerConfiguration = booterDeserializer.deserialize();
            final StartupConfiguration startupConfiguration = booterDeserializer.getProviderConfiguration();

            TypeEncodedValue forkedTestSet = providerConfiguration.getTestForFork();
            boolean readTestsFromInputStream = providerConfiguration.isReadTestsFromInStream();

            final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
            if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
            {
                classpathConfiguration.trickClassPathWhenManifestOnlyClasspath();
            }

            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
            startupConfiguration.writeSurefireTestClasspathProperty();

            final Object testSet;
            if ( forkedTestSet != null )
            {
                testSet = forkedTestSet.getDecodedValue( classLoader );
            }
            else if ( readTestsFromInputStream )
            {
                testSet = new LazyTestsToRun( originalOut );
            }
            else
            {
                testSet = null;
            }

            try
            {
                runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, originalOut );
            }
            catch ( InvocationTargetException t )
            {
                LegacyPojoStackTraceWriter stackTraceWriter =
                    new LegacyPojoStackTraceWriter( "test subystem", "no method", t.getTargetException() );
                StringBuilder stringBuilder = new StringBuilder();
                encode( stringBuilder, stackTraceWriter, false );
                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , originalOut );
            }
            catch ( Throwable t )
            {
                StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subystem", "no method", t );
                StringBuilder stringBuilder = new StringBuilder();
                encode( stringBuilder, stackTraceWriter, false );
                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", originalOut );
            }
            // Say bye.
            encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
            originalOut.flush();
            // noinspection CallToSystemExit
            exit( 0, EXIT, reader, false );
        }
        catch ( Throwable t )
        {
            // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace
            // noinspection UseOfSystemOutOrSystemErr
            t.printStackTrace( System.err );
            // noinspection ProhibitedExceptionThrown,CallToSystemExit
            exit( 1, EXIT, reader, false );
        }
        finally
        {
            pingScheduler.cancel( true );
        }
    }

    private static CommandReader startupMasterProcessReader()
    {
        return CommandReader.getReader();
    }

    private static ScheduledFuture<?> listenToShutdownCommands( CommandReader reader )
    {
        reader.addShutdownListener( createExitHandler( reader ) );
        AtomicBoolean pingDone = new AtomicBoolean( true );
        reader.addNoopListener( createPingHandler( pingDone ) );
        return JVM_TERMINATOR.scheduleAtFixedRate( createPingJob( pingDone, reader ),
                                                   0, PING_TIMEOUT_IN_SECONDS, SECONDS );
    }

    private static CommandListener createPingHandler( final AtomicBoolean pingDone )
    {
        return new CommandListener()
        {
            public void update( Command command )
            {
                pingDone.set( true );
            }
        };
    }

    private static CommandListener createExitHandler( final CommandReader reader )
    {
        return new CommandListener()
        {
            public void update( Command command )
            {
                exit( 1, command.toShutdownData(), reader, true );
            }
        };
    }

    private static Runnable createPingJob( final AtomicBoolean pingDone, final CommandReader reader  )
    {
        return new Runnable()
        {
            public void run()
            {
                boolean hasPing = pingDone.getAndSet( false );
                if ( !hasPing )
                {
                    exit( 1, KILL, reader, true );
                }
            }
        };
    }

    private static void encodeAndWriteToOutput( String string, PrintStream out )
    {
        byte[] encodeBytes = encodeStringForForkCommunication( string );
        out.write( encodeBytes, 0, encodeBytes.length );
    }

    private static void exit( int returnCode, Shutdown shutdownType, CommandReader reader, boolean stopReaderOnExit )
    {
        switch ( shutdownType )
        {
            case KILL:
                Runtime.getRuntime().halt( returnCode );
            case EXIT:
                if ( stopReaderOnExit )
                {
                    reader.stop();
                }
                launchLastDitchDaemonShutdownThread( returnCode );
                System.exit( returnCode );
            case DEFAULT:
                // refers to shutdown=testset, but not used now, keeping reader open
            default:
                break;
        }
    }

    private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration,
                                                 ProviderConfiguration providerConfiguration,
                                                 PrintStream originalSystemOut )
        throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
    {
        final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut );

        return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration,
                                                false );
    }

    private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
                                                                 PrintStream originalSystemOut )
    {
        final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
        return SurefireReflector.createForkingReporterFactoryInCurrentClassLoader( trimStackTrace, originalSystemOut );
    }

    private static ScheduledExecutorService createJvmTerminator()
    {
        ThreadFactory threadFactory = newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-"
                                                            + SYSTEM_EXIT_TIMEOUT_IN_SECONDS
                                                            + "sec" );
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
        executor.setMaximumPoolSize( 1 );
        executor.prestartCoreThread();
        return executor;
    }

    @SuppressWarnings( "checkstyle:emptyblock" )
    private static void launchLastDitchDaemonShutdownThread( final int returnCode )
    {
        JVM_TERMINATOR.schedule( new Runnable()
        {
            public void run()
            {
                Runtime.getRuntime().halt( returnCode );
            }
        }, SYSTEM_EXIT_TIMEOUT_IN_SECONDS, SECONDS );
    }

    private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory,
                                                             ProviderConfiguration providerConfiguration,
                                                             boolean insideFork,
                                                             StartupConfiguration startupConfig,
                                                             boolean restoreStreams )
        throws TestSetFailedException, InvocationTargetException
    {
        final PrintStream orgSystemOut = System.out;
        final PrintStream orgSystemErr = System.err;
        // Note that System.out/System.err are also read in the "ReporterConfiguration" instatiation
        // in createProvider below. These are the same values as here.

        try
        {
            return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfiguration, factory )
                .invoke( testSet );
        }
        finally
        {
            if ( restoreStreams && System.getSecurityManager() == null )
            {
                System.setOut( orgSystemOut );
                System.setErr( orgSystemErr );
            }
        }
    }

    private static SurefireProvider createProviderInCurrentClassloader( StartupConfiguration startupConfiguration1,
                                                                       boolean isInsideFork,
                                                                       ProviderConfiguration providerConfiguration,
                                                                       Object reporterManagerFactory1 )
    {
        BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory1, isInsideFork );
        bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
        bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        bpf.setClassLoaders( classLoader );
        bpf.setTestArtifactInfo( providerConfiguration.getTestArtifact() );
        bpf.setProviderProperties( providerConfiguration.getProviderProperties() );
        bpf.setRunOrderParameters( providerConfiguration.getRunOrderParameters() );
        bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() );
        bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() );
        bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() );
        bpf.setShutdown( providerConfiguration.getShutdown() );
        String providerClass = startupConfiguration1.getActualClassName();
        return (SurefireProvider) ReflectionUtils.instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
    }
}
最关键的方法是上述代码中:
 private static SurefireProvider createProviderInCurrentClassloader(StartupConfiguration startupConfiguration1, boolean isInsideFork, ProviderConfiguration providerConfiguration, Object reporterManagerFactory1)
  {
    BaseProviderFactory bpf = new BaseProviderFactory((ReporterFactory)reporterManagerFactory1, isInsideFork);
    bpf.setTestRequest(providerConfiguration.getTestSuiteDefinition());
    bpf.setReporterConfiguration(providerConfiguration.getReporterConfiguration());
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    bpf.setClassLoaders(classLoader);
    bpf.setTestArtifactInfo(providerConfiguration.getTestArtifact());
    bpf.setProviderProperties(providerConfiguration.getProviderProperties());
    bpf.setRunOrderParameters(providerConfiguration.getRunOrderParameters());
    bpf.setDirectoryScannerParameters(providerConfiguration.getDirScannerParams());
    bpf.setMainCliOptions(providerConfiguration.getMainCliOptions());
    bpf.setSkipAfterFailureCount(providerConfiguration.getSkipAfterFailureCount());
    bpf.setShutdown(providerConfiguration.getShutdown());
    String providerClass = startupConfiguration1.getActualClassName();
    return (SurefireProvider)ReflectionUtils.instantiateOneArg(classLoader, providerClass, ProviderParameters.class, bpf);
  }
ReflectionUtils 来自 surefire-api.jar(2.19.1)  代码:

public static Object instantiateOneArg(ClassLoader classLoader, String className, Class<?> param1Class, Object param1)
  {
    try
    {
      Class<?> aClass = loadClass(classLoader, className);
      Constructor constructor = getConstructor(aClass, new Class[] { param1Class });
      return constructor.newInstance(new Object[] { param1 });
    }
    catch (InvocationTargetException e)
    {
      throw new SurefireReflectionException(e.getTargetException());
    }
    catch (InstantiationException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
  
  public static Object instantiateTwoArgs(ClassLoader classLoader, String className, Class<?> param1Class, Object param1, Class param2Class, Object param2)
  {
    try
    {
      Class<?> aClass = loadClass(classLoader, className);
      Constructor constructor = getConstructor(aClass, new Class[] { param1Class, param2Class });
      return constructor.newInstance(new Object[] { param1, param2 });
    }
    catch (InvocationTargetException e)
    {
      throw new SurefireReflectionException(e.getTargetException());
    }
    catch (InstantiationException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
  
  public static void invokeSetter(Object o, String name, Class<?> value1clazz, Object value)
  {
    Method setter = getMethod(o, name, new Class[] { value1clazz });
    invokeSetter(o, setter, value);
  }
  
  public static Object invokeSetter(Object target, Method method, Object value)
  {
    return invokeMethodWithArray(target, method, new Object[] { value });
  }
  
  public static Object invokeMethodWithArray(Object target, Method method, Object... args)
  {
    try
    {
      return method.invoke(target, args);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (InvocationTargetException e)
    {
      throw new SurefireReflectionException(e.getTargetException());
    }
  }
  
  public static Object invokeMethodWithArray2(Object target, Method method, Object... args)
    throws InvocationTargetException
  {
    try
    {
      return method.invoke(target, args);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
  
  public static Object instantiateObject(String className, Class[] types, Object[] params, ClassLoader classLoader)
  {
    Class<?> clazz = loadClass(classLoader, className);
    Constructor constructor = getConstructor(clazz, types);
    return newInstance(constructor, params);
  }
  
  public static Class<?> tryLoadClass(ClassLoader classLoader, String className)
  {
    try
    {
      return classLoader.loadClass(className);
    }
    catch (NoClassDefFoundError localNoClassDefFoundError) {}catch (ClassNotFoundException localClassNotFoundException) {}
    return null;
  }
  
  public static Class<?> loadClass(ClassLoader classLoader, String className)
  {
    try
    {
      return classLoader.loadClass(className);
    }
    catch (NoClassDefFoundError e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (ClassNotFoundException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
public static Object newInstance(Constructor constructor, Object... params)
  {
    try
    {
      return constructor.newInstance(params);
    }
    catch (InvocationTargetException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (InstantiationException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
  
  public static <T> T instantiate(ClassLoader classLoader, String classname, Class<T> returnType)
  {
    try
    {
      Class<?> clazz = loadClass(classLoader, classname);
      return returnType.cast(clazz.newInstance());
    }
    catch (InstantiationException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
  
  public static Object instantiateOneArg(ClassLoader classLoader, String className, Class<?> param1Class, Object param1)
  {
    try
    {
      Class<?> aClass = loadClass(classLoader, className);
      Constructor constructor = getConstructor(aClass, new Class[] { param1Class });
      return constructor.newInstance(new Object[] { param1 });
    }
    catch (InvocationTargetException e)
    {
      throw new SurefireReflectionException(e.getTargetException());
    }
    catch (InstantiationException e)
    {
      throw new SurefireReflectionException(e);
    }
    catch (IllegalAccessException e)
    {
      throw new SurefireReflectionException(e);
    }
  }
传入的是SurefireProvider 类型,其实它是接口,代码如下:

public abstract interface SurefireProvider
{
  public abstract Iterable<Class<?>> getSuites();
  
  public abstract RunResult invoke(Object paramObject)
    throws TestSetFailedException, ReporterException, InvocationTargetException;
  
  public abstract void cancel();
}
还有一个很关键的接口扩展 了SurefireProvider ,它就是AbstractProvider 接口:

public abstract class AbstractProvider
  implements SurefireProvider
{
  private final Thread creatingThread = Thread.currentThread();
  
  public void cancel()
  {
    synchronized (this.creatingThread)
    {
      if (this.creatingThread.isAlive()) {
        this.creatingThread.interrupt();
      }
    }
  }
}
然后 surefire-testng.jar 中TestNGProvider代码:

public class TestNGProvider
    extends AbstractProvider
{
    private final Map<String, String> providerProperties;

    private final ReporterConfiguration reporterConfiguration;

    private final ClassLoader testClassLoader;

    private final ScanResult scanResult;

    private final TestRequest testRequest;

    private final ProviderParameters providerParameters;

    private final RunOrderCalculator runOrderCalculator;

    private final List<CommandLineOption> mainCliOptions;

    private final CommandReader commandsReader;

    private TestsToRun testsToRun;

    public TestNGProvider( ProviderParameters bootParams )
    {
        // don't start a thread in CommandReader while we are in in-plugin process
        commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
        providerParameters = bootParams;
        testClassLoader = bootParams.getTestClassLoader();
        runOrderCalculator = bootParams.getRunOrderCalculator();
        providerProperties = bootParams.getProviderProperties();
        testRequest = bootParams.getTestRequest();
        reporterConfiguration = bootParams.getReporterConfiguration();
        scanResult = bootParams.getScanResult();
        mainCliOptions = bootParams.getMainCliOptions();
    }

    public RunResult invoke( Object forkTestSet )
        throws TestSetFailedException
    {
        if ( isFailFast() && commandsReader != null )
        {
            registerPleaseStopListener();
        }

        final ReporterFactory reporterFactory = providerParameters.getReporterFactory();
        final RunListener reporter = reporterFactory.createReporter();
        /**
         * {@link org.apache.maven.surefire.report.ConsoleOutputCapture#startCapture(ConsoleOutputReceiver)}
         * called in prior to initializing variable {@link #testsToRun}
         */
        startCapture( (ConsoleOutputReceiver) reporter );

        RunResult runResult;
        try
        {
            if ( isTestNGXmlTestSuite( testRequest ) )
            {
                if ( commandsReader != null )
                {
                    commandsReader.awaitStarted();
                }
                TestNGXmlTestSuite testNGXmlTestSuite = newXmlSuite();
                testNGXmlTestSuite.locateTestSets();
                testNGXmlTestSuite.execute( reporter );
            }
            else
            {
                if ( testsToRun == null )
                {
                    if ( forkTestSet instanceof TestsToRun )
                    {
                        testsToRun = (TestsToRun) forkTestSet;
                    }
                    else if ( forkTestSet instanceof Class )
                    {
                        testsToRun = fromClass( (Class<?>) forkTestSet );
                    }
                    else
                    {
                        testsToRun = scanClassPath();
                    }
                }

                if ( commandsReader != null )
                {
                    registerShutdownListener( testsToRun );
                    commandsReader.awaitStarted();
                }
                TestNGDirectoryTestSuite suite = newDirectorySuite();
                suite.execute( testsToRun, reporter );
            }
        }
        finally
        {
            runResult = reporterFactory.close();
        }
        return runResult;
    }

    boolean isTestNGXmlTestSuite( TestRequest testSuiteDefinition )
    {
        Collection<File> suiteXmlFiles = testSuiteDefinition.getSuiteXmlFiles();
        return !suiteXmlFiles.isEmpty() && !hasSpecificTests();
    }

    private boolean isFailFast()
    {
        return providerParameters.getSkipAfterFailureCount() > 0;
    }

    private int getSkipAfterFailureCount()
    {
        return isFailFast() ? providerParameters.getSkipAfterFailureCount() : 0;
    }

    private void registerShutdownListener( final TestsToRun testsToRun )
    {
        commandsReader.addShutdownListener( new CommandListener()
        {
            public void update( Command command )
            {
                testsToRun.markTestSetFinished();
            }
        } );
    }

    private void registerPleaseStopListener()
    {
        commandsReader.addSkipNextTestsListener( new CommandListener()
        {
            public void update( Command command )
            {
                FailFastEventsSingleton.getInstance().setSkipOnNextTest();
            }
        } );
    }

    private TestNGDirectoryTestSuite newDirectorySuite()
    {
        return new TestNGDirectoryTestSuite( testRequest.getTestSourceDirectory().toString(), providerProperties,
                                             reporterConfiguration.getReportsDirectory(), getTestFilter(),
                                             mainCliOptions, getSkipAfterFailureCount() );
    }

    private TestNGXmlTestSuite newXmlSuite()
    {
        return new TestNGXmlTestSuite( testRequest.getSuiteXmlFiles(),
                                       testRequest.getTestSourceDirectory().toString(),
                                       providerProperties,
                                       reporterConfiguration.getReportsDirectory(), getSkipAfterFailureCount() );
    }

    public Iterable<Class<?>> getSuites()
    {
        if ( isTestNGXmlTestSuite( testRequest ) )
        {
            return Collections.emptySet();
        }
        else
        {
            testsToRun = scanClassPath();
            return testsToRun;
        }
    }

    private TestsToRun scanClassPath()
    {
        final TestsToRun scanned = scanResult.applyFilter( null, testClassLoader );
        return runOrderCalculator.orderTestClasses( scanned );
    }

    private boolean hasSpecificTests()
    {
        TestListResolver specificTestPatterns = testRequest.getTestListResolver();
        return !specificTestPatterns.isEmpty() && !specificTestPatterns.isWildcard();
    }

    private TestListResolver getTestFilter()
    {
        TestListResolver filter = optionallyWildcardFilter( testRequest.getTestListResolver() );
        return filter.isWildcard() ? getEmptyTestListResolver() : filter;
    }
}
ProviderParameters接口代码:
public abstract interface ProviderParameters
{
  /**
   * @deprecated
   */
  public abstract DirectoryScanner getDirectoryScanner();
  
  public abstract ScanResult getScanResult();
  
  public abstract RunOrderCalculator getRunOrderCalculator();
  
  public abstract ReporterFactory getReporterFactory();
  
  public abstract ConsoleLogger getConsoleLogger();
  
  @Deprecated
  public abstract DirectoryScannerParameters getDirectoryScannerParameters();
  
  public abstract ReporterConfiguration getReporterConfiguration();
  
  public abstract TestRequest getTestRequest();
  
  public abstract ClassLoader getTestClassLoader();
  
  public abstract Map<String, String> getProviderProperties();
  
  public abstract TestArtifactInfo getTestArtifactInfo();
  
  public abstract List<CommandLineOption> getMainCliOptions();
  
  public abstract int getSkipAfterFailureCount();
  
  public abstract boolean isInsideFork();
  
  public abstract Shutdown getShutdown();
ScanResult接口代码:
public abstract interface ScanResult
{
  public abstract int size();
  
  public abstract String getClassName(int paramInt);
  
  public abstract TestsToRun applyFilter(ScannerFilter paramScannerFilter, ClassLoader paramClassLoader);
  
  public abstract List<Class<?>> getClassesSkippedByValidation(ScannerFilter paramScannerFilter, ClassLoader paramClassLoader);
  
  public abstract void writeTo(Map<String, String> paramMap);
}
TestsToRun代码:

public class TestsToRun
  implements Iterable<Class<?>>
{
  private final List<Class<?>> locatedClasses;
  private volatile boolean finished;
  private int iteratedCount;
  
  public TestsToRun(Set<Class<?>> locatedClasses)
  {
    this.locatedClasses = new ArrayList(locatedClasses);
  }
  
  public static TestsToRun fromClass(Class<?> clazz)
    throws TestSetFailedException
  {
    return new TestsToRun(Collections.singleton(clazz));
  }
  
  public Iterator<Class<?>> iterated()
  {
    return newWeakIterator();
  }
  
  public Iterator<Class<?>> iterator()
  {
    return new ClassesIterator(null);
  }
  
  private final class ClassesIterator
    extends CloseableIterator<Class<?>>
  {
    private final Iterator<Class<?>> it = TestsToRun.this.locatedClasses.iterator();
    private int iteratedCount;
    
    private ClassesIterator() {}
    
    protected boolean isClosed()
    {
      return TestsToRun.this.isFinished();
    }
    
    protected boolean doHasNext()
    {
      return this.it.hasNext();
    }
    
    protected Class<?> doNext()
    {
      Class<?> nextTest = (Class)this.it.next();
      TestsToRun.this.iteratedCount = Math.max(++this.iteratedCount, TestsToRun.this.iteratedCount);
      return nextTest;
    }
    
    protected void doRemove() {}
    
    public void remove()
    {
      throw new UnsupportedOperationException("unsupported remove");
    }
  }
  
  public final void markTestSetFinished()
  {
    this.finished = true;
  }
  
  public final boolean isFinished()
  {
    return this.finished;
  }
  
  public String toString()
  {
    StringBuilder sb = new StringBuilder("TestsToRun: [");
    for (Class<?> clazz : this) {
      sb.append(' ').append(clazz.getName());
    }
    sb.append(']');
    return sb.toString();
  }
  
  public boolean containsAtLeast(int atLeast)
  {
    return containsAtLeast(iterator(), atLeast);
  }
  
  private boolean containsAtLeast(Iterator<Class<?>> it, int atLeast)
  {
    for (int i = 0; i < atLeast; i++)
    {
      if (!it.hasNext()) {
        return false;
      }
      it.next();
    }
    return true;
  }
  
  public boolean containsExactly(int items)
  {
    Iterator<Class<?>> it = iterator();
    return (containsAtLeast(it, items)) && (!it.hasNext());
  }
  
  public boolean allowEagerReading()
  {
    return true;
  }
  
  public Class<?>[] getLocatedClasses()
  {
    if (!allowEagerReading()) {
      throw new IllegalStateException("Cannot eagerly read");
    }
    Collection<Class<?>> result = new ArrayList();
    for (Class<?> clazz : this) {
      result.add(clazz);
    }
    return (Class[])result.toArray(new Class[result.size()]);
  }
  
  public Class<?> getClassByName(String className)
  {
    for (Class<?> clazz : this) {
      if (clazz.getName().equals(className)) {
        return clazz;
      }
    }
    return null;
  }
  
  private Iterator<Class<?>> newWeakIterator()
  {
    final Iterator<Class<?>> it = this.locatedClasses.subList(0, this.iteratedCount).iterator();
    new CloseableIterator()
    {
      protected boolean isClosed()
      {
        return TestsToRun.this.isFinished();
      }
      
      protected boolean doHasNext()
      {
        return it.hasNext();
      }
      
      protected Class<?> doNext()
      {
        return (Class)it.next();
      }
      
      protected void doRemove() {}
      
      public void remove()
      {
        throw new UnsupportedOperationException("unsupported remove");
      }
    };
  }
}

TestNGXmlTestSuite类代码:

import static org.apache.maven.surefire.testng.TestNGExecutor.run;

/**
 * Handles suite xml file definitions for TestNG.
 *
 * @author jkuhnert
 * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
 */
final class TestNGXmlTestSuite
        extends TestSuite
{
    private final List<File> suiteFiles;

    private List<String> suiteFilePaths;

    private final String testSourceDirectory;

    private final Map<String, String> options;

    private final File reportsDirectory;

    private final int skipAfterFailureCount;

    /**
     * Creates a testng testset to be configured by the specified
     * xml file(s). The XML files are suite definitions files according to TestNG DTD.
     */
    TestNGXmlTestSuite( List<File> suiteFiles, String testSourceDirectory, Map<String, String> confOptions,
                        File reportsDirectory, int skipAfterFailureCount )
    {
        this.suiteFiles = suiteFiles;
        this.options = confOptions;
        this.testSourceDirectory = testSourceDirectory;
        this.reportsDirectory = reportsDirectory;
        this.skipAfterFailureCount = skipAfterFailureCount;
    }

    void execute( RunListener reporter )
        throws TestSetFailedException
    {
        if ( suiteFilePaths == null )
        {
            throw new IllegalStateException( "You must call locateTestSets before calling execute" );
        }
        startTestSuite( reporter );
        run( suiteFilePaths, testSourceDirectory, options, reporter, reportsDirectory, skipAfterFailureCount );
        finishTestSuite( reporter );
    }

    void locateTestSets()
        throws TestSetFailedException
    {
        if ( suiteFilePaths != null )
        {
            throw new IllegalStateException( "You can't call locateTestSets twice" );
        }

        if ( suiteFiles.isEmpty() )
        {
            throw new IllegalStateException( "No suite files were specified" );
        }

        suiteFilePaths = new ArrayList<String>();

        for ( File suiteFile : suiteFiles )
        {
            if ( !suiteFile.isFile() )
            {
                throw new TestSetFailedException( "Suite file " + suiteFile + " is not a valid file" );
            }
            suiteFilePaths.add( suiteFile.getAbsolutePath() );
        }
    }

    @Override
    Map<String, String> getOptions()
    {
        return options;
    }
}
import static org.apache.maven.surefire.testng.TestNGExecutor.run; 直接执行TestNGExecutor.run

final class TestNGExecutor
{
    /** The default name for a suite launched from the maven surefire plugin */
    private static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";

    /** The default name for a test launched from the maven surefire plugin */
    private static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";

    private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =
            tryLoadClass( TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test" ) != null;

    private TestNGExecutor()
    {
        throw new IllegalStateException( "not instantiable constructor" );
    }

    static void run( Iterable<Class<?>> testClasses, String testSourceDirectory,
                            Map<String, String> options, // string,string because TestNGMapConfigurator#configure()
                            RunListener reportManager, File reportsDirectory,
                            TestListResolver methodFilter, List<CommandLineOption> mainCliOptions,
                            int skipAfterFailureCount )
        throws TestSetFailedException
    {
        TestNG testng = new TestNG( true );

        Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );

        if ( isCliDebugOrShowErrors( mainCliOptions ) )
        {
            System.out.println( "Configuring TestNG with: " + configurator.getClass().getSimpleName() );
        }

        XmlMethodSelector groupMatchingSelector = createGroupMatchingSelector( options );
        XmlMethodSelector methodNameFilteringSelector = createMethodNameFilteringSelector( methodFilter );

        Map<String, SuiteAndNamedTests> suitesNames = new HashMap<String, SuiteAndNamedTests>();

        List<XmlSuite> xmlSuites = new ArrayList<XmlSuite>();
        for ( Class<?> testClass : testClasses )
        {
            TestMetadata metadata = findTestMetadata( testClass );

            SuiteAndNamedTests suiteAndNamedTests = suitesNames.get( metadata.suiteName );
            if ( suiteAndNamedTests == null )
            {
                suiteAndNamedTests = new SuiteAndNamedTests();
                suiteAndNamedTests.xmlSuite.setName( metadata.suiteName );
                configurator.configure( suiteAndNamedTests.xmlSuite, options );
                xmlSuites.add( suiteAndNamedTests.xmlSuite );

                suitesNames.put( metadata.suiteName, suiteAndNamedTests );
            }

            XmlTest xmlTest = suiteAndNamedTests.testNameToTest.get( metadata.testName );
            if ( xmlTest == null )
            {
                xmlTest = new XmlTest( suiteAndNamedTests.xmlSuite );
                xmlTest.setName( metadata.testName );
                addSelector( xmlTest, groupMatchingSelector );
                addSelector( xmlTest, methodNameFilteringSelector );
                xmlTest.setXmlClasses( new ArrayList<XmlClass>() );

                suiteAndNamedTests.testNameToTest.put( metadata.testName, xmlTest );
            }

            xmlTest.getXmlClasses().add( new XmlClass( testClass.getName() ) );
        }

        testng.setXmlSuites( xmlSuites );
        configurator.configure( testng, options );
        postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,
                       extractVerboseLevel( options ) );
        testng.run();
    }

    private static boolean isCliDebugOrShowErrors( List<CommandLineOption> mainCliOptions )
    {
        return mainCliOptions.contains( CommandLineOption.LOGGING_LEVEL_DEBUG )
            || mainCliOptions.contains( CommandLineOption.SHOW_ERRORS );
    }

    private static TestMetadata findTestMetadata( Class<?> testClass )
    {
        TestMetadata result = new TestMetadata();
        if ( HAS_TEST_ANNOTATION_ON_CLASSPATH )
        {
            Test testAnnotation = findAnnotation( testClass, Test.class );
            if ( null != testAnnotation )
            {
                if ( !StringUtils.isBlank( testAnnotation.suiteName() ) )
                {
                    result.suiteName = testAnnotation.suiteName();
                }

                if ( !StringUtils.isBlank( testAnnotation.testName() ) )
                {
                    result.testName = testAnnotation.testName();
                }
            }
        }
        return result;
    }

    private static <T extends Annotation> T findAnnotation( Class<?> clazz, Class<T> annotationType )
    {
        if ( clazz == null )
        {
            return null;
        }

        T result = clazz.getAnnotation( annotationType );
        if ( result != null )
        {
            return result;
        }

        return findAnnotation( clazz.getSuperclass(), annotationType );
    }

    private static class TestMetadata
    {
        private String testName = DEFAULT_SUREFIRE_TEST_NAME;

        private String suiteName = DEFAULT_SUREFIRE_SUITE_NAME;
    }

    private static class SuiteAndNamedTests
    {
        private XmlSuite xmlSuite = new XmlSuite();

        private Map<String, XmlTest> testNameToTest = new HashMap<String, XmlTest>();
    }

    private static void addSelector( XmlTest xmlTest, XmlMethodSelector selector )
    {
        if ( selector != null )
        {
            xmlTest.getMethodSelectors().add( selector );
        }
    }

    @SuppressWarnings( "checkstyle:magicnumber" )
    private static XmlMethodSelector createMethodNameFilteringSelector( TestListResolver methodFilter )
        throws TestSetFailedException
    {
        if ( methodFilter != null && !methodFilter.isEmpty() )
        {
            // the class is available in the testClassPath
            String clazzName = "org.apache.maven.surefire.testng.utils.MethodSelector";
            try
            {
                Class<?> clazz = Class.forName( clazzName );
                Method method = clazz.getMethod( "setTestListResolver", TestListResolver.class );
                method.invoke( null, methodFilter );
            }
            catch ( Exception e )
            {
                throw new TestSetFailedException( e.getMessage(), e );
            }

            XmlMethodSelector xms = new XmlMethodSelector();

            xms.setName( clazzName );
            // looks to need a high value
            xms.setPriority( 10000 );

            return xms;
        }
        else
        {
            return null;
        }
    }

    @SuppressWarnings( "checkstyle:magicnumber" )
    private static XmlMethodSelector createGroupMatchingSelector( Map<String, String> options )
        throws TestSetFailedException
    {
        final String groups = options.get( ProviderParameterNames.TESTNG_GROUPS_PROP );
        final String excludedGroups = options.get( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );

        if ( groups == null && excludedGroups == null )
        {
            return null;
        }

        // the class is available in the testClassPath
        final String clazzName = "org.apache.maven.surefire.testng.utils.GroupMatcherMethodSelector";
        try
        {
            Class<?> clazz = Class.forName( clazzName );

            // HORRIBLE hack, but TNG doesn't allow us to setup a method selector instance directly.
            Method method = clazz.getMethod( "setGroups", String.class, String.class );
            method.invoke( null, groups, excludedGroups );
        }
        catch ( Exception e )
        {
            throw new TestSetFailedException( e.getMessage(), e );
        }

        XmlMethodSelector xms = new XmlMethodSelector();

        xms.setName( clazzName );
        // looks to need a high value
        xms.setPriority( 9999 );

        return xms;
    }

    static void run( List<String> suiteFiles, String testSourceDirectory,
                            Map<String, String> options, // string,string because TestNGMapConfigurator#configure()
                            RunListener reportManager, File reportsDirectory, int skipAfterFailureCount )
        throws TestSetFailedException
    {
        TestNG testng = new TestNG( true );
        Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );
        configurator.configure( testng, options );
        postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,
                       extractVerboseLevel( options ) );
        testng.setTestSuites( suiteFiles );
        testng.run();
    }

    private static Configurator getConfigurator( String className )
    {
        try
        {
            return (Configurator) Class.forName( className ).newInstance();
        }
        catch ( InstantiationException e )
        {
            throw new RuntimeException( e );
        }
        catch ( IllegalAccessException e )
        {
            throw new RuntimeException( e );
        }
        catch ( ClassNotFoundException e )
        {
            throw new RuntimeException( e );
        }
    }

    private static void postConfigure( TestNG testNG, String sourcePath, final RunListener reportManager,
                                       File reportsDirectory, int skipAfterFailureCount, int verboseLevel )
    {
        // 0 (default): turn off all TestNG output
        testNG.setVerbose( verboseLevel );

        TestNGReporter reporter = createTestNGReporter( reportManager );
        testNG.addListener( (Object) reporter );

        if ( skipAfterFailureCount > 0 )
        {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            testNG.addListener( instantiate( cl, "org.apache.maven.surefire.testng.utils.FailFastNotifier",
                                             Object.class ) );
            testNG.addListener( new FailFastListener( createStoppable( reportManager, skipAfterFailureCount ) ) );
        }

        // FIXME: use classifier to decide if we need to pass along the source dir (only for JDK14)
        if ( sourcePath != null )
        {
            testNG.setSourcePath( sourcePath );
        }

        testNG.setOutputDirectory( reportsDirectory.getAbsolutePath() );
    }

    private static Stoppable createStoppable( final RunListener reportManager, int skipAfterFailureCount )
    {
        final AtomicInteger currentFaultCount = new AtomicInteger( skipAfterFailureCount );

        return new Stoppable()
        {
            public void fireStopEvent()
            {
                if ( countDownToZero( currentFaultCount ) )
                {
                    FailFastEventsSingleton.getInstance().setSkipOnNextTest();
                }

                reportManager.testExecutionSkippedByUser();
            }
        };
    }

    // If we have access to IResultListener, return a ConfigurationAwareTestNGReporter
    // But don't cause NoClassDefFoundErrors if it isn't available; just return a regular TestNGReporter instead
    private static TestNGReporter createTestNGReporter( RunListener reportManager )
    {
        try
        {
            Class.forName( "org.testng.internal.IResultListener" );
            Class c = Class.forName( "org.apache.maven.surefire.testng.ConfigurationAwareTestNGReporter" );
            @SuppressWarnings( "unchecked" ) Constructor<?> ctor = c.getConstructor( RunListener.class );
            return (TestNGReporter) ctor.newInstance( reportManager );
        }
        catch ( InvocationTargetException e )
        {
            throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e.getCause() );
        }
        catch ( ClassNotFoundException e )
        {
            return new TestNGReporter( reportManager );
        }
        catch ( Exception e )
        {
            throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e );
        }
    }

    private static int extractVerboseLevel( Map<String, String> options )
        throws TestSetFailedException
    {
        try
        {
            String verbose = options.get( "surefire.testng.verbose" );
            return verbose == null ? 0 : Integer.parseInt( verbose );
        }
        catch ( NumberFormatException e )
        {
            throw new TestSetFailedException( "Provider property 'surefire.testng.verbose' should refer to "
                                                  + "number -1 (debug mode), 0, 1 .. 10 (most detailed).", e );
        }
    }
}

run 方法中直接调用

 TestNG testng = new TestNG( true ); 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值