Ant JUnit Nice Output Formatter
Nice output formatter for Apache Ant + JUnit
I've seached for nice Apache Ant formatter which do not writes full stack trace in console/file and just writes what methods are passed, what methods are failed, and if failed - where and why they failed (and only trace about methods from test class). I haven't found any. So I've written one.
For example:
[junit] ----------------------------------------------------------
[junit] Testsuite: com.undefined.MyTest
[junit] Ran [0.322] testMethodOne ... OK
[junit] Ran [0.023] testMethodOne... OK
[junit] Ran [0.333] testMethodTwo ... FAILED
[junit] Ran [0.343] testMethodThree ... FAILED
[junit]
[junit] Testcase: testMethodTwo(com.undefined.MyTest): FAILED
[junit] (AssertionFailedError): expected:<Bender> but was:<null>
[junit] (MyTest) assertEqualsInfo: 887
[junit] (MyTest) testGetUserInfoByUserId: 188
There is ant task for this:
<target name="easy-test" depends="test-clean, compile"> <mkdir dir="out/junit"/> <junit printsummary="off" fork="on" failureproperty="test.failed" showoutput="off" dir="out" outputtoformatters="false" filtertrace="on" > <classpath> .... </classpath> <formatter classname="com.undefined.testing.OneLinerFormatter" usefile="false" /> <batchtest fork="on" todir="out/junit" filtertrace="on"> <fileset dir="src/java/test"> <include name="**/*Test.java"/> </fileset> </batchtest> </junit> <fail if="test.failed">tests.failed=${test.failed}</fail> </target>
And finally, the implementation:
package com.undefined.testing; import java.io.BufferedReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.text.NumberFormat; import java.util.Hashtable; import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.framework.AssertionFailedError; import junit.framework.Test; import org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter; import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest; import org.apache.tools.ant.taskdefs.optional.junit.JUnitVersionHelper; import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.StringUtils; /** * Prints a single lines of tests to a specified Writer. * Inspired by the BriefJUnitResultFormatter and * XMLJUnitResultFormatter. * * @see FormatterElement * @see BriefJUnitResultFormatter * @see XMLJUnitResultFormatter */ public class OneLinerFormatter implements JUnitResultFormatter { private final String TAB_STR = " "; private final boolean showCausesLines = true; // (\w+\.)+(\w+)\((\w+).(?:\w+):(\d+)\) private final Pattern traceLinePattern = Pattern.compile("(\\w+\\.)+(\\w+)\\((\\w+).(?:\\w+):(\\d+)\\)"); /** * Where to write the log to. */ private OutputStream out; /** * Used for writing the results. */ private PrintWriter output; /** * Used as part of formatting the results. */ private StringWriter results; /** * Used for writing formatted results to. */ private PrintWriter resultWriter; /** * Formatter for timings. */ private NumberFormat numberFormat = NumberFormat.getInstance(); /** * Output suite has written to System.out */ private String systemOutput = null; /** * Output suite has written to System.err */ private String systemError = null; /** * tests that failed. */ private Hashtable failedTests = new Hashtable(); /** * Timing helper. */ private Hashtable testStarts = new Hashtable(); /** * Constructor for OneLinerFormatter. */ public OneLinerFormatter() { results = new StringWriter(); resultWriter = new PrintWriter(results); } /** * Sets the stream the formatter is supposed to write its results to. * @param out the output stream to write to */ public void setOutput(OutputStream out) { this.out = out; output = new PrintWriter(out); } /** * @see JUnitResultFormatter#setSystemOutput(String) */ public void setSystemOutput(String out) { systemOutput = out; } /** * @see JUnitResultFormatter#setSystemError(String) */ public void setSystemError(String err) { systemError = err; } /** * The whole testsuite started. * @param suite the test suite */ public void startTestSuite(JUnitTest suite) { if (output == null) { return; // Quick return - no output do nothing. } StringBuffer sb = new StringBuffer(StringUtils.LINE_SEP); sb.append("----------------------------------------------------------"); sb.append(StringUtils.LINE_SEP); sb.append("Testsuite: "); sb.append(suite.getName()); sb.append(StringUtils.LINE_SEP); output.write(sb.toString()); output.flush(); } /** * The whole testsuite ended. * @param suite the test suite */ public void endTestSuite(JUnitTest suite) { StringBuffer sb = new StringBuffer("Tests run: "); sb.append(suite.runCount()); sb.append(", Failures: "); sb.append(suite.failureCount()); sb.append(", Errors: "); sb.append(suite.errorCount()); sb.append(", Time elapsed: "); sb.append(numberFormat.format(suite.getRunTime() / 1000.0)); sb.append(" sec"); sb.append(StringUtils.LINE_SEP); sb.append(StringUtils.LINE_SEP); // append the err and output streams to the log if (systemOutput != null && systemOutput.length() > 0) { sb.append("------------- Standard Output ---------------") .append(StringUtils.LINE_SEP) .append(systemOutput) .append("------------- ---------------- ---------------") .append(StringUtils.LINE_SEP); } if (systemError != null && systemError.length() > 0) { sb.append("------------- Standard Error -----------------") .append(StringUtils.LINE_SEP) .append(systemError) .append("------------- ---------------- ---------------") .append(StringUtils.LINE_SEP); } if (output != null) { try { output.write(sb.toString()); resultWriter.close(); output.write(results.toString()); output.flush(); } finally { if (out != System.out && out != System.err) { FileUtils.close(out); } } } } /** * A test started. * @param test a test */ public void startTest(Test test) { testStarts.put(test, new Long(System.currentTimeMillis())); } /** * A test ended. * @param test a test */ public void endTest(Test test) { // Fix for bug #5637 - if a junit.extensions.TestSetup is // used and throws an exception during setUp then startTest // would never have been called if (!testStarts.containsKey(test)) { startTest(test); } boolean failed = failedTests.containsKey(test); Long l = (Long) testStarts.get(test); output.write("Ran ["); output.write(((System.currentTimeMillis() - l.longValue()) / 1000.0) + "] "); output.write(getTestName(test) + " ... " + (failed ? "FAILED" : "OK")); output.write(StringUtils.LINE_SEP); output.flush(); } /** * Interface TestListener for JUnit <= 3.4. * * <p>A Test failed. * @param test a test * @param t the exception thrown by the test */ public void addFailure(Test test, Throwable t) { formatError("\tFAILED", test, t); } /** * Interface TestListener for JUnit > 3.4. * * <p>A Test failed. * @param test a test * @param t the assertion failed by the test */ public void addFailure(Test test, AssertionFailedError t) { addFailure(test, (Throwable) t); } /** * A test caused an error. * @param test a test * @param error the error thrown by the test */ public void addError(Test test, Throwable error) { formatError("\tCaused an ERROR", test, error); } /** * Get test name * * @param test a test * @return test name */ protected String getTestName(Test test) { if (test == null) { return "null"; } else { return /* JUnitVersionHelper.getTestCaseClassName(test) + ": " + */ JUnitVersionHelper.getTestCaseName(test); } } /** * Get test case full class name * * @param test a test * @return test full class name */ protected String getTestCaseClassName(Test test) { if (test == null) { return "null"; } else { return JUnitVersionHelper.getTestCaseClassName(test); } } /** * Format the test for printing.. * @param test a test * @return the formatted testname */ protected String formatTest(Test test) { if (test == null) { return "Null Test: "; } else { return "Testcase: " + test.toString() + ":"; } } /** * Format an error and print it. * @param type the type of error * @param test the test that failed * @param error the exception that the test threw */ protected synchronized void formatError(String type, Test test, Throwable error) { if (test != null) { failedTests.put(test, test); endTest(test); } resultWriter.println(formatTest(test) + type); resultWriter.println(TAB_STR + "(" + error.getClass().getSimpleName() + "): " + ((error.getMessage() != null) ? error.getMessage() : error)); if (showCausesLines) { // resultWriter.append(StringUtils.LINE_SEP); resultWriter.println(filterErrorTrace(test, error)); } resultWriter.println(); /* String strace = JUnitTestRunner.getFilteredTrace(error); resultWriter.println(strace); resultWriter.println(); */ } protected String filterErrorTrace(Test test, Throwable error) { String trace = StringUtils.getStackTrace(error); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); StringReader sr = new StringReader(trace); BufferedReader br = new BufferedReader(sr); String line; try { while ((line = br.readLine()) != null) { if (line.indexOf(getTestCaseClassName(test)) != -1) { Matcher matcher = traceLinePattern.matcher(line); // pw.println(matcher + ": " + matcher.find()); if (matcher.find()) { pw.print(TAB_STR); pw.print("(" + matcher.group(3) + ") "); pw.print(matcher.group(2) + ": "); pw.println(matcher.group(4)); } else { pw.println(line); } } } } catch (Exception e) { return trace; // return the treca unfiltered } return sw.toString(); }
page revision: 1, last edited: 08 Apr 2010 16:32