DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

How To Exclude Methods In TestNG: IMethodSelector

11.08.2008
| 5803 views |
  • submit to reddit
        How to exclude methods in TestNG ?

Details are in the javadoc associated with the code below: one java class: copy-paste it (in a 'test' package) and run it (Java5 or 6, TestNG 5.8). You need to add testng-jdk5.jar to your classpath.

This class is also written in response to a <a href="http://stackoverflow.com/questions/9044#275342"><b>StackOverflow Code Challenge</b></a>.

package test;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;

import org.testng.IMethodSelector;
import org.testng.IMethodSelectorContext;
import org.testng.ISuite;
import org.testng.ITestListener;
import org.testng.ITestNGMethod;
import org.testng.ITestRunnerFactory;
import org.testng.TestNG;
import org.testng.TestNGCommandLineArgs;
import org.testng.TestNGException;
import org.testng.TestRunner;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.reporters.JUnitXMLReporter;
import org.testng.reporters.TestHTMLReporter;
import org.testng.xml.XmlMethodSelector;
import org.testng.xml.XmlTest;

/**
 * Allow to mark all @Test annotated public method, and avoid un-annotated public methods. <br />
 * In response to a <a href="http://stackoverflow.com/questions/9044#275342">code challenge</a> on StackOverflow, where the goal is to 
 * "specify a class wide group on a TestNG test case". <br />
 * The @Test annotation on the base class is the righ move, BUT it would also includes public methods 
 * <i>not</i> annotated as Test methods. <p />
 * This shows how to refine the Method lists by using a custom MethodSelector in TestNG (5.8). 
 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
 */
@Test(groups = { "aGlobalGroup" })
public class Base {
	
	/**
	 * 
	 */
	@BeforeClass
	 public final void setUp() {
		   System.out.println("Base class: @BeforeClass");
	 }


	/**
	 * Test not part a 'aGlobalGroup', but still included in that group due to the class annotation. <br />
	 * Will be executed even if the TestNG class tested is a sub-class.
	 */
	@Test(groups = { "aLocalGroup" })
	 public final void aFastTest() {
	   System.out.println("Base class: Fast test");
	 }

	/**
	 * Test not part a 'aGlobalGroup', but still included in that group due to the class annotation. <br />
	 * Will be executed even if the TestNG class tested is a sub-class.
	 */
	@Test(groups = { "aLocalGroup" })
	 public final void aSlowTest() {
	    System.out.println("Base class: Slow test");
	    //throw new IllegalArgumentException("oups");
	 }

	 /**
	  * Should not be executed. <br />
	  * Yet the global annotation Test on the class would include it in the TestNG methods...
	 */
	public final void notATest() {
	   System.out.println("Base class: NOT a test");
	 }
	
	/**
	 * SubClass of a TestNG class. Some of its methods are TestNG methods, other are not. <br />
	 * The goal is to check if a group specify in the super-class will include methods of this class. <br />
	 * And to avoid including too much methods, such as public methods not intended to be TestNG methods.
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	public static class Extended extends Base
	{
		/**
		 * Test not part a 'aGlobalGroup', but still included in that group due to the super-class annotation. <br />
		 * Will be executed even if the TestNG class tested is a sub-class.
		 */
		@Test
		public final void anExtendedTest() {
		   System.out.println("Extended class: An Extended test");
		}

		/**
		 * Should not be executed. <br />
		 * Yet the global annotation Test on the class would include it in the TestNG methods...
		 */	
		public final void notAnExtendedTest() {
			System.out.println("Extended class: NOT an Extended test");
		}
	}
	
	/**
	 * a TestRunnerFactory is needed to register a custom XmlMethodSelector to the XMLTest. <br />
	 * That XmlMethodSelector will be used to build a IMethodSelector
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	public static class MyTestRunnerFactory implements ITestRunnerFactory
	{

		/**
		 * Register a custom XmlMethodSelector. <br />
		 * One with high priority, executed before other (default) XmlMethodSelector.
		 * @see org.testng.ITestRunnerFactory#newTestRunner(org.testng.ISuite, org.testng.xml.XmlTest)
		 */
		@Override
		public final TestRunner newTestRunner(final ISuite aSuite, final XmlTest anXMLTest) 
		{
			final XmlMethodSelector anXmlMethodSelector = new XmlMethodSelector();
			anXmlMethodSelector.setName(MyMethodSelector.class.getName());
			anXmlMethodSelector.setPriority(1);
			anXMLTest.getMethodSelectors().add(anXmlMethodSelector);
			
			// rest is a copy of the super() method.
			final TestRunner runner = new TestRunner(aSuite, anXMLTest, false /*skipFailedInvocationCounts */);
			runner.addListener(new TestHTMLReporter());
			runner.addListener(new JUnitXMLReporter());
			
			return runner;
		}
	}
	
	/**
	 * Custom MethodSelector to avoid selecting non-annotated public methods. <br />
	 * That way, a global Test annotation at the class level does not parasite non-test methods.
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	public static class  MyMethodSelector implements IMethodSelector {

		/**
		 * default serial ID.
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * Any public non-annotated method will be ignored by <i>all</i> MethodSelector. <br />
		 * Any other one will be ignore, but only be this MethodSelector. Other might select them.
		 * @see org.testng.IMethodSelector#includeMethod(org.testng.IMethodSelectorContext, org.testng.ITestNGMethod, boolean)
		 */
		@Override
		public final boolean includeMethod(final IMethodSelectorContext context, final ITestNGMethod method, 
				final boolean isTestMethod) 
		{
			System.out.println(method.getMethodName() + ": " + method.isTest() + "****** " + stringArrayToString(method.getGroups()));
			boolean stop = true;
			final Annotation[] someAnnotations = method.getMethod().getAnnotations(); 
			for (int i = 0; i < someAnnotations.length; i++) 
			{
				final Annotation anAnnotation = someAnnotations[i];
				if(anAnnotation instanceof Test)
				{
					stop = false;
					break;
				}
			}
			//stop = false; // if this line is un-commented, all methods are taken into account
			if(stop)
			{
				System.out.println("IGNORE AND EXCLUDE not-annotated public method '" + method.getMethodName() + "'");
				context.setStopped(true);
			}
			return false;
		}
		private static String stringArrayToString(final String[] aStringArray)
		{
			final StringBuilder msg =  new StringBuilder("[");
			if(aStringArray != null)
			{
				for (int i = 0; i < aStringArray.length; i++) {
					msg.append(aStringArray[i]);
					if(i<(aStringArray.length-1))
					{
						msg.append(", ");
					}
				}
			}
			msg.append("]");
			return msg.toString();
		}
		private List<ITestNGMethod> testMethods;
		/**
		 * @see org.testng.IMethodSelector#setTestMethods(java.util.List)
		 */
		@Override
		public final void setTestMethods(final List<ITestNGMethod> someTestMethods) 
		{
			this.testMethods = someTestMethods;
			System.out.println(this.testMethods);
		}
		
	}
	
	/**
	 * Custom TestNG needed because the original TestNG does not interpret the -testrunfactory option. <br />
	 * That bug has been <a href="http://forums.opensymphony.com/thread.jspa%3FmessageID%3D112438%26%23112402">reported 
	 * two years ago</a>...
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	public static class MyTestNG extends TestNG
	{
		/**
		 * The TestNG entry point for command line execution. <br />
		 * Execute a TestNG on Extended class for the group 'aGlobalGroup'.
		 * @param argv the TestNG command line parameters.
		 */
		public static void main(final String[] argv) {
			System.out.println("77743332111".replaceAll("(\\d)\\1+", "$1"));
			final String[] myArgv = new String[] {
					"-groups", "aGlobalGroup",
					"-testclass", "test.Base$Extended",
					"-suitename", "TestNG tests",
					"-testname", "Group Inheritance",
					"-testrunfactory", "test.Base$MyTestRunnerFactory",
					"-log", "10"
			};
			final TestNG testng = privateMain(myArgv, null);
		    System.exit(testng.getStatus());
		}
		/**
		 * Make sure that MyTestNG is created by the main TESTNG function. <br />
		 * That way, {@link MyTestNG#configure(Map)} is called, and tesntgfactory is taken into account.
		 * @param argv
		 * @param listener 
		 * @return MyTestNG instance, with a TestRunnerFactory created if specified.
		 */
		@SuppressWarnings("unchecked")
		public static TestNG privateMain(final String[] argv, final ITestListener listener) 
		{
			final Map arguments= checkConditions(TestNGCommandLineArgs.parseCommandLine(argv));
		    
		    final MyTestNG result = new MyTestNG();
		    if (null != listener) {
		    	result.addListener(listener);
		    }

		    result.configure(arguments);
		    try {
		    	result.run();
		    }
		    catch(final TestNGException ex) 
		    {
		    	if (TestRunner.getVerbose() > 1) 
		    	{
		    		ex.printStackTrace(System.out);
		    	}
		    	else {
		    		System.err.println("[ERROR]: " + ex.getMessage());
		    	}
		    	result.setStatus(HAS_FAILURE);
		    }
		    return result;
		}
		@Override
		protected final void setStatus(final int status) {
			super.setStatus(status);
		}
		
		/**
		 * Configure the TestNG instance by reading the settings provided in the map. <br />
		 * Takes into the TESTRUNNER_FACTORY_COMMAND_OPT, which is still ignored 
		 * even <a href="http://forums.opensymphony.com/thread.jspa?messageID=112438%26%23112402">after being 
		 * reported missing</a> to Cedric BEUST.
		 * @param cmdLineArgs map of settings
		 * @see TestNGCommandLineArgs for setting keys
		 */
		@Override
		@SuppressWarnings({"unchecked"})
		public final void configure(final Map cmdLineArgs) 
		{
			super.configure(cmdLineArgs);
			final Class aTestRunnerFactoryClass = (Class) cmdLineArgs.get(TestNGCommandLineArgs.TESTRUNNER_FACTORY_COMMAND_OPT);
			if(aTestRunnerFactoryClass != null)
			{
				setTestRunnerFactoryClass(aTestRunnerFactoryClass);
			}
		}
	}
	/**
	 * Redirection to the main of MyTestNG. <br />
	 * All TestNG parameters are hard-coded there.
	 * @param argv
	 */
	public static void main(final String[] argv) {
		MyTestNG.main(argv);
	}
}