// net.mindview.atunit.AtUnit11.java // TIJ4 Chapter Annotations, Exercise 11, page 1104 // Add an @TestNote annotation to @Unit, so that the accompanying // note is simply displayed during testing. /* My solution to one of the exercises in * Thinking in Java 4th Edition (by Bruce Eckel). * It compiles and runs correctly using JDK 1.6.0 * @author Greg Gordon * @author www.greggordon.org * April, 2009 */ /** In package annotations: * // annotations.TestNoteEx11.java * package annotations; * import net.mindview.atunit.*; * import net.mindview.util.*; * public class TestNoteEx11 { * public String methodOne() { return "methodOne"; } * public int methodTwo() { * System.out.println("This is methodTwo"); * return 2; * } * @TestNote (note = "methodOne should return a String") * @Test void _methodOne() { * assert methodOne().equals("methodOne"); * } * @TestNote (note = "methodTwo contains a println() statement") * @Test void _methodTwo() { * assert methodTwo() == 2; * } * public static void main(String[] args) throws Exception { * OSExecute.command( * "java net.mindview.atunit.AtUnitEx11 TestNoteEx11"); * } * } * In package new.mindview.atunit: * //: net/mindview/atunit/TestNote.java * // The @TestNote tag. * package net.mindview.atunit; * import java.lang.annotation.*; * @Target(ElementType.METHOD) * @Retention(RetentionPolicy.RUNTIME) * public @interface TestNote { * public String note() default "no note"; * } **/ //: net/mindview/atunit/AtUnit11.java // An annotation-based unit-test framework. // {RunByHand} package net.mindview.atunit; import java.lang.reflect.*; import java.io.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class AtUnitEx11 implements ProcessFiles.Strategy { static Class testClass; static List failedTests= new ArrayList(); static long testsRun = 0; static long failures = 0; public static void main(String[] args) throws Exception { ClassLoader.getSystemClassLoader() .setDefaultAssertionStatus(true); // Enable asserts new ProcessFiles(new AtUnitEx11(), "class").start(args); if(failures == 0) print("OK (" + testsRun + " tests)"); else { print("(" + testsRun + " tests)"); print("\n>>> " + failures + " FAILURE" + (failures > 1 ? "S" : "") + " <<<"); for(String failed : failedTests) print(" " + failed); } } public void process(File cFile) { try { String cName = ClassNameFinder.thisClass( BinaryFile.read(cFile)); if(!cName.contains(".")) return; // Ignore unpackaged classes testClass = Class.forName(cName); } catch(Exception e) { throw new RuntimeException(e); } TestMethods testMethods = new TestMethods(); Method creator = null; Method cleanup = null; for(Method m : testClass.getDeclaredMethods()) { testMethods.addIfTestMethod(m); if(creator == null) creator = checkForCreatorMethod(m); if(cleanup == null) cleanup = checkForCleanupMethod(m); } if(testMethods.size() > 0) { if(creator == null) try { if(!Modifier.isPublic(testClass .getDeclaredConstructor().getModifiers())) { print("Error: " + testClass + " default constructor must be public"); System.exit(1); } } catch(NoSuchMethodException e) { // Synthesized default constructor; OK } print(testClass.getName()); } for(Method m : testMethods) { printnb(" . " + m.getName() + " "); if(m.isAnnotationPresent(TestNote.class)) // if @TestNote present print("Note: " + m.getAnnotation(TestNote.class).note()); // print note try { Object testObject = createTestObject(creator); boolean success = false; try { if(m.getReturnType().equals(boolean.class)) success = (Boolean)m.invoke(testObject); else { m.invoke(testObject); success = true; // If no assert fails } } catch(InvocationTargetException e) { // Actual exception is inside e: print(e.getCause()); } print(success ? "" : "(failed)"); testsRun++; if(!success) { failures++; failedTests.add(testClass.getName() + ": " + m.getName()); } if(cleanup != null) cleanup.invoke(testObject, testObject); } catch(Exception e) { throw new RuntimeException(e); } } } static class TestMethods extends ArrayList { void addIfTestMethod(Method m) { if(m.getAnnotation(Test.class) == null) return; if(!(m.getReturnType().equals(boolean.class) || m.getReturnType().equals(void.class))) throw new RuntimeException("@Test method" + " must return boolean or void"); m.setAccessible(true); // In case it's private, etc. add(m); } } private static Method checkForCreatorMethod(Method m) { if(m.getAnnotation(TestObjectCreate.class) == null) return null; if(!m.getReturnType().equals(testClass)) throw new RuntimeException("@TestObjectCreate " + "must return instance of Class to be tested"); if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) throw new RuntimeException("@TestObjectCreate " + "must be static."); m.setAccessible(true); return m; } private static Method checkForCleanupMethod(Method m) { if(m.getAnnotation(TestObjectCleanup.class) == null) return null; if(!m.getReturnType().equals(void.class)) throw new RuntimeException("@TestObjectCleanup " + "must return void"); if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) throw new RuntimeException("@TestObjectCleanup " + "must be static."); if(m.getParameterTypes().length == 0 || m.getParameterTypes()[0] != testClass) throw new RuntimeException("@TestObjectCleanup " + "must take an argument of the tested type."); m.setAccessible(true); return m; } private static Object createTestObject(Method creator) { if(creator != null) { try { return creator.invoke(testClass); } catch(Exception e) { throw new RuntimeException("Couldn't run " + "@TestObject (creator) method."); } } else { // Use the default constructor: try { return testClass.newInstance(); } catch(Exception e) { throw new RuntimeException("Couldn't create a " + "test object. Try using a @TestObject method."); } } } } ///:~