001: /*
002: * @(#)InterfaceTestCase.java
003: *
004: * Copyright (C) 2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.junit.v1.iftc;
028:
029: import junit.framework.TestCase;
030:
031: import java.io.StringWriter;
032: import java.io.PrintWriter;
033:
034: import java.util.Stack;
035:
036: /**
037: * A subclass of TestCase to ease the requirements of creating an
038: * interface test. The tests should be thought of as "contract tests".
039: * Subclasses can call <tt>createImplObject()</tt> to create a new instance
040: * of a subclass of <tt>interfaceClass</tt>, which is generated from the
041: * <tt>ImplFactory</tt> passed into the constructor.
042: * <P>
043: * Subclasses that want to use the InterfaceTestSuite helper class will need
044: * to specify a constructor similar to:
045: * <PRE>
046: * public MyClassTest( String name, ImplFactory f )
047: * {
048: * super( name, MyClass.class, f );
049: * }
050: * </PRE>
051: * where <tt>MyClass</tt> is the interface or base class under test.
052: * <P>
053: * As of October 30, 2002, the InterfaceTestCase has a slightly different
054: * behavior when the factory instance is an implementation of ICxFactory.
055: * In this scenario, it will store all the instantiated objects in the stack,
056: * and each instantiated object will be passed to the ICxFactory instance
057: * during the test's normal tearDown. If the ICxFactory throws an exception
058: * during any of the tearDown calls, they will be stored up and reported
059: * in a single exception. Therefore, if you want this functionality, then
060: * you will need to ensure that your <tt>tearDown()</tt> method calls the
061: * super <tt>tearDown()</tt>.
062: * <P>
063: * Even though JUnit 3.8+ allows for a TestCase to have a default (no-arg)
064: * constructor, the <tt>InterfaceTestCase</tt> does not support this. The
065: * benefits simply aren't there for interface tests: they will still have to
066: * create a constructor which passes <tt>InterfaceTestCase</tt> which class
067: * is being tested. Since a constructor is required anyway, the little extra
068: * effort to add two arguments to the constructor and call to the super
069: * is trivial compared to not needing the constructor at all.
070: * <P>
071: * As of 08-Dec-2002, the returned name of the test can include the class's
072: * name, without the package, to improve traceability. This will allow
073: * the user to be able to see in which specific test class an error occured
074: * through the Ant JUnit report mechanism. This is enabled by default, but
075: * can be disabled by setting the Java system-wide property
076: * "net.sourceforge.groboutils.junit.v1.iftc.InterfaceTestCase.no-classname"
077: * to <tt>true</tt>,
078: * which is dynamically checked at runtime at each call.
079: *
080: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
081: * @version $Date: 2003/02/10 22:52:20 $
082: * @since March 2, 2002
083: * @see ImplFactory
084: * @see ICxFactory
085: * @see InterfaceTestSuite
086: */
087: public abstract class InterfaceTestCase extends TestCase {
088: // package protected for test-case use.
089: static final String DONT_USE_CLASSNAME = InterfaceTestCase.class
090: .getName()
091: + ".no-classname";
092:
093: private ImplFactory factory;
094: private Class interfaceClass;
095:
096: // Due to possible memory-leak, this stack will only contain
097: // instantiated objects when the factory is of type ICxFactory.
098: private Stack instantiatedObjects = new Stack();
099:
100: // As a slight optimization, we will cache the check if the
101: // factory is an instance of ICxFactory or not.
102: private boolean isICxFactory = false;
103:
104: // allows for manual setting of the classname display in the output name
105: private Boolean useClassInName = null;
106:
107: /**
108: * The standard constructor used by JUnit up to version 3.7.
109: *
110: * @param name the name of the test to execute.
111: * @param interfaceClass the class which this test case tests.
112: * @param f the factory which will create specific subclass instances.
113: */
114: public InterfaceTestCase(String name, Class interfaceClass,
115: ImplFactory f) {
116: super (name);
117: if (interfaceClass == null || f == null) {
118: throw new IllegalArgumentException("no null arguments");
119: }
120:
121: // need to ensure that a common test coding error didn't occur...
122: assertTrue(
123: "Interface under test argument ("
124: + interfaceClass.getName()
125: + ") is the same as the current test's class ("
126: + getClass().getName()
127: + "). The correct usage is to pass in the Class object which "
128: + "corresponds to the superclass or interface all instance methods "
129: + "tested must extend.", !getClass().equals(
130: interfaceClass));
131:
132: // the class can be an interface, an abstract class, or just
133: // a regular class. We don't care as long as it isn't null.
134: this .interfaceClass = interfaceClass;
135: this .factory = f;
136:
137: // cache the assertion for if the factory is an ICxFactory instance.
138: if (f instanceof ICxFactory) {
139: this .isICxFactory = true;
140: }
141: }
142:
143: /**
144: * Sets whether the classname is put in the output or not. If you don't
145: * set this value here, it will use the value of the
146: * system property described above.
147: *
148: * @since 03-Dec-2002
149: */
150: public void setUseClassInName(boolean use) {
151: this .useClassInName = new Boolean(use);
152: }
153:
154: /**
155: * Calls the stored factory to create an implemented object. Subclasses
156: * should make their own method, say <tt>getObject()</tt>, which returns
157: * this method's result, but casted to the right class.
158: * <P>
159: * This method makes an assertion that the factory's created object is not
160: * <tt>null</tt>, so that the system state is ensured. Therefore,
161: * this method will never return <tt>null</tt>. Also, this method asserts
162: * that the created object is of the correct type (as passed in through
163: * the constructor), so that it can be correctly cast without errors.
164: *
165: * @return the object created by the factory.
166: */
167: public Object createImplObject() {
168: // ensure the factory was set.
169: assertNotNull("The factory instance was never set.",
170: this .factory);
171:
172: Object o;
173: try {
174: o = this .factory.createImplObject();
175: } catch (Exception ex) {
176: // allow for the factory creation to throw exceptions.
177: fail("Factory " + this .factory.toString()
178: + " threw exception " + ex + " during creation: "
179: + exceptionToString(ex));
180: // the above call will always exit, but the compiler doesn't
181: // know that. So to make it happy the next line has been added.
182: o = null;
183: }
184: assertNotNull("The implementation factory " + this .factory
185: + " created a null.", o);
186:
187: // Since the generated object is non-null, we will store it in our
188: // stack, even if the next assert fails. This allows for correct
189: // deconstruction of *every* non-null generated object.
190: if (this .isICxFactory) {
191: this .instantiatedObjects.push(o);
192: }
193:
194: assertTrue(
195: "The implementation factory did not create a valid class: created "
196: + o.getClass().getName()
197: + ", but should have been of type "
198: + getInterfaceClass().getName() + ".",
199: getInterfaceClass().isInstance(o));
200: return o;
201: }
202:
203: /**
204: * Return the interface or abstract class this test covers.
205: *
206: * @return the interface under test.
207: */
208: public Class getInterfaceClass() {
209: return this .interfaceClass;
210: }
211:
212: /**
213: * Override the TestCase default getName so that the factory names are
214: * returned as well.
215: *
216: * @return the method name being tested, along with the factory's
217: * name.
218: */
219: public String getName() {
220: return getNamePrefix() + super .getName() + "["
221: + this .factory.toString() + "]";
222: }
223:
224: /**
225: * Ensure, for JUnit 3.7 support, that the original name() method is
226: * still supported.
227: *
228: * @return getName().
229: */
230: public String name() {
231: return getName();
232: }
233:
234: /**
235: * Send each instantiated object to the factory for cleanup.
236: *
237: * @exception Exception thrown if the super's tearDown throws an
238: * exception, or if any exceptions are thrown during the tear-down
239: * of the factory generated instances.
240: */
241: protected void tearDown() throws Exception {
242: if (this .isICxFactory) {
243: int errorCount = 0;
244: StringBuffer sb = new StringBuffer(
245: "Encountered factory tearDown exceptions: ");
246: ICxFactory cf = (ICxFactory) this .factory;
247: while (!this .instantiatedObjects.isEmpty()) {
248: try {
249: cf.tearDown(this .instantiatedObjects.pop());
250: } catch (ThreadDeath td) {
251: // never swallow thread death exceptions
252: throw td;
253: } catch (Throwable t) {
254: // catch all factory exceptions, and sum them up into
255: // a single exception at the end.
256: if (errorCount > 0) {
257: sb.append("; ");
258: }
259: sb.append(t.toString());
260:
261: ++errorCount;
262:
263: // Tell the user about this exception.
264: t.printStackTrace();
265: }
266: }
267: // only do the assertion *after* all the generated objects have
268: // been torn down.
269: assertTrue(sb.toString(), errorCount <= 0);
270: }
271:
272: // tell the super-class to tear itself down.
273: super .tearDown();
274: }
275:
276: /**
277: * Allow for easy translation of exceptions to strings, including
278: * stack traces.
279: *
280: * @param t the exception to translate into a string.
281: * @return the exception + stack trace as a string.
282: */
283: private String exceptionToString(Throwable t) {
284: if (t == null) {
285: return "<null exception>";
286: }
287: StringWriter sw = new StringWriter();
288: PrintWriter pw = new PrintWriter(sw);
289: t.printStackTrace(pw);
290: pw.flush();
291: return sw.toString();
292: }
293:
294: /**
295: * Generate the prefix for the name of this test class.
296: *
297: * @return the prefix string
298: * @since 08-Dec-2002
299: */
300: private String getNamePrefix() {
301: boolean usePrefix;
302: if (this .useClassInName != null) {
303: usePrefix = this .useClassInName.booleanValue();
304: } else {
305: usePrefix = !Boolean.getBoolean(DONT_USE_CLASSNAME);
306: }
307: String ret = "";
308: if (usePrefix) {
309: // get the classname of the current test, without the package.
310: ret = this .getClass().getName();
311: int pos = ret.lastIndexOf('.');
312: if (pos >= 0) {
313: ret = ret.substring(pos + 1);
314: }
315: ret = ret + '.';
316: }
317: return ret;
318: }
319: }
|