View Javadoc

1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   */
38  package com.gargoylesoftware.base.testing;
39  
40  import com.gargoylesoftware.base.util.DetailedIllegalArgumentException;
41  import java.lang.reflect.Constructor;
42  import java.lang.reflect.InvocationTargetException;
43  import java.lang.reflect.Method;
44  import java.util.HashMap;
45  import java.util.Iterator;
46  import java.util.Map;
47  import junit.framework.TestCase;
48  import junit.framework.TestSuite;
49  
50  /***
51   *  This class allows you to specify the order that test methods will execute in
52   *  and yet still retain the ability to have test methods found via reflection.<p>
53   *
54   * Generally, when you want to specify the order that tests will be run, you write
55   * code that looks like this.
56   * <pre>
57   * public static Test suite() {
58   *     final TestSuite suite = new TestSuite();
59   *     suite.addTest( new FooTest("testOne") );
60   *     suite.addTest( new FooTest("testTwo") );
61   *     return suite;
62   * }
63   * </pre>
64   * The problem with this approach is that when testThree() is written, if it isn't
65   * added to the suite method, it won't get executed.<p>
66   *
67   * Using OrderedTestSuite, you write code like this:
68   * <pre>
69   * public static Test suite() {
70   *     return new OrderedTestSuite(FooTest.class, new String[]{"testOne", "testTwo"});
71   * }
72   * </pre>
73   * testOne() and testTwo() will be executed in order just as in the first example.  The
74   * difference is that OrderedTestSuite will use reflection to find other methods starting
75   * with test and will execute them.  This means that if you write testThree but forget to
76   * add it to suite() then it will still get executed, albeit not in a defined order.
77   *
78   * @version  $Revision: 1.4 $
79   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
80   */
81  public final class OrderedTestSuite extends TestSuite {
82      private final Class classToTest_;
83      private final String[] orderedMethodNames_;
84  
85  
86      /***
87       *  Create an instance of this test suite
88       *
89       * @param  clazz the class that we will use to get the individual tests.
90       *      This must be a subclass of TestCase
91       * @param  methodNames The names of any methods that must be tested in order
92       */
93      public OrderedTestSuite( final Class clazz, final String[] methodNames ) {
94          if( clazz == null ) {
95              throw new NullPointerException( "clazz" );
96          }
97          if( methodNames == null ) {
98              throw new NullPointerException( "methodNames" );
99          }
100         if( TestCase.class.isAssignableFrom( clazz ) == false ) {
101             throw new DetailedIllegalArgumentException(
102                 "clazz", clazz, "Must be a subclass of TestCase" );
103         }
104         classToTest_ = clazz;
105         orderedMethodNames_ = methodNames;
106 
107         addMethodsInOrder( getMatchingMethods() );
108     }
109 
110 
111     private Map getMatchingMethods() {
112         final Map matchingMethods = new HashMap( 89 );
113 
114         final Method allMethods[] = classToTest_.getMethods();
115 
116         int i;
117         for( i = 0; i < allMethods.length; i++ ) {
118             if( isMatchingMethod( allMethods[i] ) ) {
119                 matchingMethods.put( allMethods[i].getName(), allMethods[i] );
120             }
121         }
122 
123         return matchingMethods;
124     }
125 
126 
127     private boolean isMatchingMethod( final Method method ) {
128         final String methodName = method.getName();
129 
130         if( method.getParameterTypes().length != 0 ) {
131             return false;
132         }
133 
134         if( method.getReturnType() != void.class ) {
135             return false;
136         }
137 
138         if( methodName.startsWith( "test" ) ) {
139             return true;
140         }
141 
142         int i;
143         for( i = 0; i < orderedMethodNames_.length; i++ ) {
144             if( methodName.equals( orderedMethodNames_[i] ) ) {
145                 return true;
146             }
147         }
148         return false;
149     }
150 
151 
152     private void addTest( final Method method ) {
153         try {
154             final Constructor constructor = classToTest_.getConstructor( new Class[]{String.class} );
155             final TestCase test = (TestCase)constructor.newInstance( new Object[]{method.getName()} );
156 
157             addTest( test );
158         }
159         catch( final NoSuchMethodException e ) {
160             throw new TestInitializationFailedException(
161                 "Unable to find a constructor that takes a string", e );
162         }
163         catch( final InstantiationException e ) {
164             throw new TestInitializationFailedException(
165                 "Unable to instantiate test for method " + method.getName(), e );
166         }
167         catch( final IllegalAccessException e ) {
168             throw new TestInitializationFailedException(
169                 "Unable to instantiate test for method " + method.getName(), e );
170         }
171         catch( final InvocationTargetException e ) {
172             throw new TestInitializationFailedException(
173                 "Unable to instantiate test for method" + method.getName(), e.getTargetException() );
174         }
175     }
176 
177 
178     private void addMethodsInOrder( final Map methodMap ) {
179         int i;
180         String name;
181         Method method;
182 
183         for( i = 0; i < orderedMethodNames_.length; i++ ) {
184             name = orderedMethodNames_[i];
185 
186             method = (Method)methodMap.get( name );
187             methodMap.remove( name );
188             if( method == null ) {
189                 throw new TestInitializationFailedException( "method not found [" + name + "]" );
190             }
191             addTest( method );
192         }
193 
194         final Iterator iterator = methodMap.values().iterator();
195         while( iterator.hasNext() ) {
196             method = (Method)iterator.next();
197             addTest( method );
198         }
199     }
200 }
201