From: Partlow, K. <Kenny.Partlow@McKesson.com> - 2002-07-19 17:14:57
|
Here is a TestSuiteBuilder class I use to build TestSuites for my TestCases. I got tired of having to update the implementations of Test Suite in my directory every time I wrote a unit test. This class allows you to create a TestSuite for a directory and then not have to touch it again. Example usage is in the class, but there are two static helper functions to ease the use of the class. If your package base starts with "com": TestSuiteBuilder.getTests(MyClass.class); The directory that MyClass.class resides in is the base directory, and all classes that extend TestCase in that directory and all subdirectories gets added to the suite. If you package base starts with anything besides "com", such as "org" or "junit" you need to call: TestSuiteBuilder.getTests(MyClass.class, "org"); Both of these return a TestSuite. I coudn't find anything like this in junit so I'm submitting this to the development list. *********** Code Follows *********************** package junit.framework; import java.io.File; import java.net.URL; import java.util.StringTokenizer; /** * <p><strong>TestSuiteBuilder</strong></p> * * This class does exactly what its name says. It builds * a <code>junit.framework.TestSuite</code> for you. You * should construct this class with a class from the * directory that you want to test.. * * For simplicity, this class always recurses directories * to add in all files to the TestSuite that extend * <code>junit.framework.TestCase</code>. * * A static helper class called getTests(Class) has been * provided to keep your code small. * * Example Usage: * * <code> * package com; * * import junit.framework.Test; * import junit.framework.TestSuite; * * public class TestAll extends TestSuite * { * public TestAll(String string) * { * super(string); * } * * public static Test suite() * { * return TestSuiteBuilder.getTests(TestAll.class); * } * } * </code> * * The test runner will run all tests in the com directory and * and in any subdirectories. * * @author Kenneth Partlow * */ public class TestSuiteBuilder { /** * In a J2EE application a container may contain multiple ClassLoaders. * Tomcat has three different ClassLoaders (one at the application level, * one at the container level, and one global one). If we store a file * in our application, yet use the container level <code>ClassLoader</code> * to try and find it, the file will not be on the ClassPath. Here, we * get the ClassLoader for this file, which if it is at the application * level, will return us the appropriate ClassLoader to find our file. */ private static final ClassLoader _cl = TestSuiteBuilder.class.getClassLoader(); /** * Storage for our Tests. We'll returns this to you when * you call #getTests() */ private TestSuite _suite = new TestSuite(); /** * Package where we start loading tests from. This is * used to convert from absolute paths back to relative * to the classpath. Most people use com and if you're * using com you do not need to specify the base package * in the constructor. If you're using "junit" as your * base or "org" then you need to specify those as your * base package. */ private static String _basePackage; /** * This is the default base package. */ private static final String DEFAULT_BASE_PACKAGE = "com"; /** * Builds a test suite based on the directory that your class * resides in. This class always recurses directories for * simplicity. Call getTests() after construction and you'll * have a ready made TestSuite handed back to you. * * @param c We use the directory of this class to start building * our tests from. */ public TestSuiteBuilder(Class c) { this(c, DEFAULT_BASE_PACKAGE); } /** * Builds a test suite based on the directory that your class * resides in. This class always recurses directories for * simplicity. Call getTests() after construction and you'll * have a ready made TestSuite handed back to you. * * @param c We use the directory of this class to start building * our tests from. * * @param basePackage We use this as the base package for converting * an absolute path to a relative path String. You only need to use * this constructor for your tests if your base package is not "com" */ public TestSuiteBuilder(Class c, String basePackage) { setBasePackage(basePackage); handleDirectory(getDirectory(c)); } /** * This method takes an object and retrieves the directory * that this class sits in. * * @param c The class whose director we're trying to * locate. */ public static File getDirectory(Class c) { String packageName = c.getPackage().getName(); // We'll string tok through all the decimals StringTokenizer tok = new StringTokenizer(packageName, "."); StringBuffer buffer = new StringBuffer(packageName.length()); // Recreate our file path with File.separatorChar instead // of the decimal point separator. while (tok.hasMoreTokens()) { buffer.append(tok.nextToken()); buffer.append(File.separatorChar); } // We don't want the last slash on there buffer.deleteCharAt(buffer.length()-1); // remove the class name so that we just have the directory return getResourceAsFile(buffer.toString()); } /** * this file is called to set the base package for a set of * tests. This helps in conversion from absolute file path * back to relative path for building a test suite. */ private void setBasePackage(String basePackage) { StringBuffer buffer = new StringBuffer(5); buffer.append(File.separator); buffer.append(basePackage); buffer.append(File.separator); _basePackage = buffer.toString(); } /** * Static helper function to retrieve the test suite * for a class. * * @param c We use this classes absolute path to * as the base directory for the tests. * * @return TestSuite the test suite for the class */ public static TestSuite getTests(Class c) { return new TestSuiteBuilder(c).getTests(); } /** * Static helper function to retrieve the test suite * for a class. * * @param c We use this classes absolute path to * as the base directory for the tests. * * @param basePackage We use this as the base package for converting * an absolute path to a relative path String. You only need to use * this constructor for your tests if your base package is not "com" * * @return TestSuite the test suite for the class */ public static TestSuite getTests(Class c, String basePackage) { return new TestSuiteBuilder(c, basePackage).getTests(); } /** * Return the suite of tests for a given directory. * * @return TestSuite The suite of tests. */ public TestSuite getTests() { return _suite; } /** * Takes care of processing for a directory. * * @param file This should be a directory. * * @param recursive This tells later iterations if we're * recursing through all directories. * */ private void handleDirectory(File file) { routeFiles(file.listFiles()); } /** * Routes a list of files individually. * calls routeFile. * * @param files The list of files to route. * * @see #routeFile(File) */ private void routeFiles(File [] files) { for (int i=0; i<files.length; i++) routeFile(files[i]); } /** * Routes a <code>File</code> based on its type. If * the file is a directory send it to the directory * handler (only if we're recursing directories) and * if the file is actually a file, send it to the * file handler. * * @param file The file to route */ private void routeFile(File file) { if (file.isDirectory()) handleDirectory(file); else handleFile(file); } /** * Handles a file. First converts the file to a relative * to the class path string and then adds the class to the * TestSuite. * * @param file The file to convert to a Class and add to * the test suite. */ private void handleFile(File file) { String path = getRelativeClassPath(file.getPath()); // If path is null, this wasn't a class file. if (path == null) return; addToTestSuite(path); } /** * Converts an absolute path name to a path name relative * to the classpath. It will also put in "."s for "\\"s * and strip the ".class" from the end. * * @param absolute the abolute path of the class we want. * * @return String The converted string. */ private String getRelativeClassPath(String absolute) { absolute = substring(absolute, _basePackage); if (absolute == null) return null; // Try to strip the off the "$.class" (inner classes). String relative = stripFromEnd(absolute, "$.class"); // If it wasn't an inner class stip off the ".class" if (relative == null) relative = stripFromEnd(absolute, ".class"); // check to see if item wasn't a .class file if (relative == null) return null; // item was a class file so convert file separators // to period. return convertFileSeparatorsToPeriods(relative); } /** * Strips the string starting from a given pattern. * A use for this is converting an absolute path to * a relative path. * * Given: * * string - "c:\\apps\\classes\\com\\mckesson\\Utilities.class * pattern - "\\com\\" * * The function will return: * * "com\\mckesson\\Utilities.class" * */ public String substring(String string, String pattern) { if (string == null) return null; int index = string.indexOf(pattern); if (index == -1) return null; return string.substring(index + 1); } /** * Strips the end of a string of a particular pattern. * An exmaple would be stripping a string of an extension. * * @param string - Given the string "com\\mckesson\\Utilities.class" * * @param pattern - and the pattern ".class" * * @return String we'll return "com\\mckesson\\Utilities" * */ public String stripFromEnd(String string, String pattern) { int index = string.lastIndexOf(pattern); if (index == -1) return null; return string.substring(0, index); } /** * This method takes an object and retrieves the directory * that this object sits in as a File object */ public String convertFileSeparatorsToPeriods(String className) { String fileSeparator = new String(File.separator); // We'll string tok through all the decimals StringTokenizer tok = new StringTokenizer(className, fileSeparator); StringBuffer buffer = new StringBuffer(className.length()); // Recreate our file path with File.separatorChar instead // of the decimal point separator. while (tok.hasMoreTokens()) { buffer.append(tok.nextToken()); buffer.append("."); } // We don't want the last slash on there buffer.deleteCharAt(buffer.length()-1); return buffer.toString(); } /** * Add a fully qualified Class to the test suite. * * @param path The fully qualified string representation * of a class. Ex. "com.mckesson.StringUtilities". This * is passed to Class.forName(). */ private void addToTestSuite(String path) { try { addToTestSuite(Class.forName(path)); } catch (ClassNotFoundException cnfe) { System.out.println("Should not get here in this context"); } } /** * Check to see if this Class is a TestCase * * @param c Is this Class a TestCase? * * @return true if the Class is a TestCase, false otherwise */ private boolean isTestCase(Class c) { return TestCase.class.isAssignableFrom(c); } /** * Adds a test to the test suite if it * extends TestCase. * * @param c The class to try and add to the test suite. */ private void addToTestSuite(Class c) { if (isTestCase(c)) { _suite.addTestSuite(c); } } /** This class will search a file in the following order * * 1) treat the name as a good file name, search as an absolute file, or * a relative path to the application * * 2) if not found, search the application class path * * Use '/' to specify file path when use relative path * */ public static File getResourceAsFile(String file) { // find resource as absolute path File absolute = new File(file); if ( absolute.exists() ) return absolute; URL url = getResourceAsUrl(file); // resource could not be found if (url == null) return null; return loadFileFromClasspath(url); } /** * This function retrieves a SystemResource as a <code>URL</code> * * The function will: * * 1) Try and treat the name as an absolute file path. * * 2) Search the application class path. * * @param url url of the resource we are trying to retrieve * * @return URL */ public static URL getResourceAsUrl(String url) { if ( _cl == null) { return ClassLoader.getSystemResource(url); } else { return _cl.getResource(url); } } /** * This method loads a file from the classpath * * @param url The url of the file to load * * @return File the file that was found * */ public static File loadFileFromClasspath(URL url) { if (url == null) return null; String fileName = url.getFile(); File relative = new File(fileName); if ( relative.exists() ) return relative; return null; } } |