KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > collections > BulkTest


1 /*
2  * Copyright 2001-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.collections;
17
18 import java.lang.reflect.Constructor JavaDoc;
19 import java.lang.reflect.InvocationTargetException JavaDoc;
20 import java.lang.reflect.Method JavaDoc;
21 import java.lang.reflect.Modifier JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.Arrays JavaDoc;
24 import java.util.List JavaDoc;
25
26 import junit.framework.TestCase;
27 import junit.framework.TestSuite;
28
29 /**
30  * A {@link TestCase} that can define both simple and bulk test methods.
31  * <p>
32  * A <I>simple test method</I> is the type of test traditionally
33  * supplied by by {@link TestCase}. To define a simple test, create a public
34  * no-argument method whose name starts with "test". You can specify the
35  * the name of simple test in the constructor of <code>BulkTest</code>;
36  * a subsequent call to {@link TestCase#run} will run that simple test.
37  * <p>
38  * A <I>bulk test method</I>, on the other hand, returns a new instance
39  * of <code>BulkTest</code>, which can itself define new simple and bulk
40  * test methods. By using the {@link #makeSuite} method, you can
41  * automatically create a hierarchal suite of tests and child bulk tests.
42  * <p>
43  * For instance, consider the following two classes:
44  *
45  * <Pre>
46  * public class TestSet extends BulkTest {
47  *
48  * private Set set;
49  *
50  * public TestSet(Set set) {
51  * this.set = set;
52  * }
53  *
54  * public void testContains() {
55  * boolean r = set.contains(set.iterator().next()));
56  * assertTrue("Set should contain first element, r);
57  * }
58  *
59  * public void testClear() {
60  * set.clear();
61  * assertTrue("Set should be empty after clear", set.isEmpty());
62  * }
63  * }
64  *
65  *
66  * public class TestHashMap extends BulkTest {
67  *
68  * private Map makeFullMap() {
69  * HashMap result = new HashMap();
70  * result.put("1", "One");
71  * result.put("2", "Two");
72  * return result;
73  * }
74  *
75  * public void testClear() {
76  * Map map = makeFullMap();
77  * map.clear();
78  * assertTrue("Map empty after clear", map.isEmpty());
79  * }
80  *
81  * public BulkTest bulkTestKeySet() {
82  * return new TestSet(makeFullMap().keySet());
83  * }
84  *
85  * public BulkTest bulkTestEntrySet() {
86  * return new TestSet(makeFullMap().entrySet());
87  * }
88  * }
89  * </Pre>
90  *
91  * In the above examples, <code>TestSet</code> defines two
92  * simple test methods and no bulk test methods; <code>TestHashMap</code>
93  * defines one simple test method and two bulk test methods. When
94  * <code>makeSuite(TestHashMap.class).run</code> is executed,
95  * <I>five</I> simple test methods will be run, in this order:<P>
96  *
97  * <Ol>
98  * <Li>TestHashMap.testClear()
99  * <Li>TestHashMap.bulkTestKeySet().testContains();
100  * <Li>TestHashMap.bulkTestKeySet().testClear();
101  * <Li>TestHashMap.bulkTestEntrySet().testContains();
102  * <Li>TestHashMap.bulkTestEntrySet().testClear();
103  * </Ol>
104  *
105  * In the graphical junit test runners, the tests would be displayed in
106  * the following tree:<P>
107  *
108  * <UL>
109  * <LI>TestHashMap</LI>
110  * <UL>
111  * <LI>testClear
112  * <LI>bulkTestKeySet
113  * <UL>
114  * <LI>testContains
115  * <LI>testClear
116  * </UL>
117  * <LI>bulkTestEntrySet
118  * <UL>
119  * <LI>testContains
120  * <LI>testClear
121  * </UL>
122  * </UL>
123  * </UL>
124  *
125  * A subclass can override a superclass's bulk test by
126  * returning <code>null</code> from the bulk test method. If you only
127  * want to override specific simple tests within a bulk test, use the
128  * {@link #ignoredTests} method.<P>
129  *
130  * Note that if you want to use the bulk test methods, you <I>must</I>
131  * define your <code>suite()</code> method to use {@link #makeSuite}.
132  * The ordinary {@link TestSuite} constructor doesn't know how to
133  * interpret bulk test methods.
134  *
135  * @author Paul Jack
136  * @version $Id: BulkTest.java,v 1.10 2004/02/18 01:20:35 scolebourne Exp $
137  */

138 public class BulkTest extends TestCase implements Cloneable JavaDoc {
139
140
141     // Note: BulkTest is Cloneable to make it easier to construct
142
// BulkTest instances for simple test methods that are defined in
143
// anonymous inner classes. Basically we don't have to worry about
144
// finding weird constructors. (And even if we found them, technically
145
// it'd be illegal for anyone but the outer class to invoke them).
146
// Given one BulkTest instance, we can just clone it and reset the
147
// method name for every simple test it defines.
148

149
150     /**
151      * The full name of this bulk test instance. This is the full name
152      * that is compared to {@link #ignoredTests} to see if this
153      * test should be ignored. It's also displayed in the text runner
154      * to ease debugging.
155      */

156     String JavaDoc verboseName;
157
158
159     /**
160      * Constructs a new <code>BulkTest</code> instance that will run the
161      * specified simple test.
162      *
163      * @param name the name of the simple test method to run
164      */

165     public BulkTest(String JavaDoc name) {
166         super(name);
167         this.verboseName = getClass().getName();
168     }
169
170
171     /**
172      * Creates a clone of this <code>BulkTest</code>.<P>
173      *
174      * @return a clone of this <code>BulkTest</code>
175      */

176     public Object JavaDoc clone() {
177         try {
178             return super.clone();
179         } catch (CloneNotSupportedException JavaDoc e) {
180             throw new Error JavaDoc(); // should never happen
181
}
182     }
183
184
185     /**
186      * Returns an array of test names to ignore.<P>
187      *
188      * If a test that's defined by this <code>BulkTest</code> or
189      * by one of its bulk test methods has a name that's in the returned
190      * array, then that simple test will not be executed.<P>
191      *
192      * A test's name is formed by taking the class name of the
193      * root <code>BulkTest</code>, eliminating the package name, then
194      * appending the names of any bulk test methods that were invoked
195      * to get to the simple test, and then appending the simple test
196      * method name. The method names are delimited by periods:
197      *
198      * <pre>
199      * TestHashMap.bulkTestEntrySet.testClear
200      * </pre>
201      *
202      * is the name of one of the simple tests defined in the sample classes
203      * described above. If the sample <code>TestHashMap</code> class
204      * included this method:
205      *
206      * <pre>
207      * public String[] ignoredTests() {
208      * return new String[] { "TestHashMap.bulkTestEntrySet.testClear" };
209      * }
210      * </pre>
211      *
212      * then the entry set's clear method wouldn't be tested, but the key
213      * set's clear method would.
214      *
215      * @return an array of the names of tests to ignore, or null if
216      * no tests should be ignored
217      */

218     public String JavaDoc[] ignoredTests() {
219         return null;
220     }
221
222
223     /**
224      * Returns the display name of this <code>BulkTest</code>.
225      *
226      * @return the display name of this <code>BulkTest</code>
227      */

228     public String JavaDoc toString() {
229         return getName() + "(" + verboseName + ") ";
230     }
231
232
233     /**
234      * Returns a {@link TestSuite} for testing all of the simple tests
235      * <I>and</I> all the bulk tests defined by the given class.<P>
236      *
237      * The class is examined for simple and bulk test methods; any child
238      * bulk tests are also examined recursively; and the results are stored
239      * in a hierarchal {@link TestSuite}.<P>
240      *
241      * The given class must be a subclass of <code>BulkTest</code> and must
242      * not be abstract.<P>
243      *
244      * @param c the class to examine for simple and bulk tests
245      * @return a {@link TestSuite} containing all the simple and bulk tests
246      * defined by that class
247      */

248     public static TestSuite makeSuite(Class JavaDoc c) {
249         if (Modifier.isAbstract(c.getModifiers())) {
250             throw new IllegalArgumentException JavaDoc("Class must not be abstract.");
251         }
252         if (!BulkTest.class.isAssignableFrom(c)) {
253             throw new IllegalArgumentException JavaDoc("Class must extend BulkTest.");
254         }
255         return new BulkTestSuiteMaker(c).make();
256     }
257
258 }
259
260
261 // It was easier to use a separate class to do all the reflection stuff
262
// for making the TestSuite instances. Having permanent state around makes
263
// it easier to handle the recursion.
264
class BulkTestSuiteMaker {
265
266     /** The class that defines simple and bulk tests methods. */
267     private Class JavaDoc startingClass;
268
269     /** List of ignored simple test names. */
270     private List JavaDoc ignored;
271    
272     /** The TestSuite we're currently populating. Can change over time. */
273     private TestSuite result;
274
275     /**
276      * The prefix for simple test methods. Used to check if a test is in
277      * the ignored list.
278      */

279     private String JavaDoc prefix;
280
281     /**
282      * Constructor.
283      *
284      * @param startingClass the starting class
285      */

286     public BulkTestSuiteMaker(Class JavaDoc startingClass) {
287         this.startingClass = startingClass;
288     }
289
290     /**
291      * Makes a hierarchal TestSuite based on the starting class.
292      *
293      * @return the hierarchal TestSuite for startingClass
294      */

295     public TestSuite make() {
296          this.result = new TestSuite();
297          this.prefix = getBaseName(startingClass);
298          result.setName(prefix);
299
300          BulkTest bulk = makeFirstTestCase(startingClass);
301          ignored = new ArrayList JavaDoc();
302          String JavaDoc[] s = bulk.ignoredTests();
303          if (s != null) {
304              ignored.addAll(Arrays.asList(s));
305          }
306          make(bulk);
307          return result;
308     }
309
310     /**
311      * Appends all the simple tests and bulk tests defined by the given
312      * instance's class to the current TestSuite.
313      *
314      * @param bulk An instance of the class that defines simple and bulk
315      * tests for us to append
316      */

317     void make(BulkTest bulk) {
318         Class JavaDoc c = bulk.getClass();
319         Method JavaDoc[] all = c.getMethods();
320         for (int i = 0; i < all.length; i++) {
321             if (isTest(all[i])) addTest(bulk, all[i]);
322             if (isBulk(all[i])) addBulk(bulk, all[i]);
323         }
324     }
325
326     /**
327      * Adds the simple test defined by the given method to the TestSuite.
328      *
329      * @param bulk The instance of the class that defined the method
330      * (I know it's weird. But the point is, we can clone the instance
331      * and not have to worry about constructors.)
332      * @param m The simple test method
333      */

334     void addTest(BulkTest bulk, Method JavaDoc m) {
335         BulkTest bulk2 = (BulkTest)bulk.clone();
336         bulk2.setName(m.getName());
337         bulk2.verboseName = prefix + "." + m.getName();
338         if (ignored.contains(bulk2.verboseName)) return;
339         result.addTest(bulk2);
340     }
341
342     /**
343      * Adds a whole new suite of tests that are defined by the result of
344      * the given bulk test method. In other words, the given bulk test
345      * method is invoked, and the resulting BulkTest instance is examined
346      * for yet more simple and bulk tests.
347      *
348      * @param bulk The instance of the class that defined the method
349      * @param m The bulk test method
350      */

351     void addBulk(BulkTest bulk, Method JavaDoc m) {
352         String JavaDoc verboseName = prefix + "." + m.getName();
353         if (ignored.contains(verboseName)) return;
354         
355         BulkTest bulk2;
356         try {
357             bulk2 = (BulkTest)m.invoke(bulk, null);
358             if (bulk2 == null) return;
359         } catch (InvocationTargetException JavaDoc ex) {
360             ex.getTargetException().printStackTrace();
361             throw new Error JavaDoc(); // FIXME;
362
} catch (IllegalAccessException JavaDoc ex) {
363             ex.printStackTrace();
364             throw new Error JavaDoc(); // FIXME;
365
}
366
367         // Save current state on the stack.
368
String JavaDoc oldPrefix = prefix;
369         TestSuite oldResult = result;
370
371         prefix = prefix + "." + m.getName();
372         result = new TestSuite();
373         result.setName(m.getName());
374
375         make(bulk2);
376
377         oldResult.addTest(result);
378
379         // Restore the old state
380
prefix = oldPrefix;
381         result = oldResult;
382     }
383
384     /**
385      * Returns the base name of the given class.
386      *
387      * @param c the class
388      * @return the name of that class, minus any package names
389      */

390     private static String JavaDoc getBaseName(Class JavaDoc c) {
391         String JavaDoc name = c.getName();
392         int p = name.lastIndexOf('.');
393         if (p > 0) {
394             name = name.substring(p + 1);
395         }
396         return name;
397     }
398
399
400     // These three methods are used to create a valid BulkTest instance
401
// from a class.
402

403     private static Constructor JavaDoc getTestCaseConstructor(Class JavaDoc c) {
404         try {
405             return c.getConstructor(new Class JavaDoc[] { String JavaDoc.class });
406         } catch (NoSuchMethodException JavaDoc e) {
407             throw new IllegalArgumentException JavaDoc(c + " must provide " +
408              "a (String) constructor");
409         }
410     }
411
412     private static BulkTest makeTestCase(Class JavaDoc c, Method JavaDoc m) {
413         Constructor JavaDoc con = getTestCaseConstructor(c);
414         try {
415             return (BulkTest)con.newInstance(new String JavaDoc[] { m.getName() });
416         } catch (InvocationTargetException JavaDoc e) {
417             e.printStackTrace();
418             throw new RuntimeException JavaDoc(); // FIXME;
419
} catch (IllegalAccessException JavaDoc e) {
420             throw new Error JavaDoc(); // should never occur
421
} catch (InstantiationException JavaDoc e) {
422             throw new RuntimeException JavaDoc(); // FIXME;
423
}
424     }
425
426     private static BulkTest makeFirstTestCase(Class JavaDoc c) {
427         Method JavaDoc[] all = c.getMethods();
428         for (int i = 0; i < all.length; i++) {
429             if (isTest(all[i])) return makeTestCase(c, all[i]);
430         }
431         throw new IllegalArgumentException JavaDoc(c.getName() + " must provide "
432           + " at least one test method.");
433     }
434
435     /**
436      * Returns true if the given method is a simple test method.
437      */

438     private static boolean isTest(Method JavaDoc m) {
439         if (!m.getName().startsWith("test")) return false;
440         if (m.getReturnType() != Void.TYPE) return false;
441         if (m.getParameterTypes().length != 0) return false;
442         int mods = m.getModifiers();
443         if (Modifier.isStatic(mods)) return false;
444         if (Modifier.isAbstract(mods)) return false;
445         return true;
446     }
447
448     /**
449      * Returns true if the given method is a bulk test method.
450      */

451     private static boolean isBulk(Method JavaDoc m) {
452         if (!m.getName().startsWith("bulkTest")) return false;
453         if (m.getReturnType() != BulkTest.class) return false;
454         if (m.getParameterTypes().length != 0) return false;
455         int mods = m.getModifiers();
456         if (Modifier.isStatic(mods)) return false;
457         if (Modifier.isAbstract(mods)) return false;
458         return true;
459     }
460
461 }
462
Popular Tags