title: What is the best way to automatically generate JUnit results in xml?
body: <p>I am currently writing a series of tests in JUnit. I need to automatically export the results as XML. I was reading that the best way of doing this is by extending the RunListener class and writing the XML that way.  Below is a sample of what I have done so far, but I am struggling with how to extract information on each test that has been run.  The 'Description' class doesn't seem to have any useful get methods and I feel like I am maybe going about this the wrong way.  </p>
  
  <p>Can someone assist with how to get useful information from description (eg. test passed / failed, duration of test, test name etc) or give me some advice on what I actually should be doing?</p>
  
  <pre><code>public class XmlListener extends RunListener {
  
      private final PrintStream fWriter;
  
      public XmlListener(JUnitSystem system) {
          this(system.out());
      }
  
      public XmlListener(PrintStream writer) {
          this.fWriter = writer;
      }
  
      @Override
      public void testRunStarted(Description description) {
          fWriter.append("&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n");
      }
  
      @Override
      public void testRunFinished(Result result) {
          fWriter.append("\t\t&lt;/suite&gt;\n");
          fWriter.append("\t&lt;/suites&gt;\n");
          fWriter.append("&lt;/result&gt;\n");
          printHeader(result.getRunTime());
          printFailures(result);
          printFooter(result);
  
      }
  
      @Override
      public void testStarted(Description description) {
          fWriter.append("\t\t\t&lt;case&gt;\n");
          fWriter.append("\t\t\t\t&lt;timestamp&gt;"  + "&lt;/timestamp&gt;\n");
          fWriter.append("\t\t\t\t&lt;className&gt;"  + "&lt;/className&gt;\n");
          fWriter.append("\t\t\t\t&lt;testName&gt;"  + "&lt;/testName&gt;\n");
      }
  
      @Override
      public void testFinished(Description description) {
          fWriter.append("\t\t\t\t&lt;duration&gt;"  + "&lt;/duration&gt;\n");
          fWriter.append("\t\t\t&lt;/case&gt;\n");
          Iterator it = description.getAnnotations().iterator();
          while (it.hasNext()) {
              fWriter.append(it.next().toString());
          }
      }
  
      @Override
      public void testFailure(Failure failure) {
          fWriter.append('E');
      }
  
      @Override
      public void testIgnored(Description description) {
          fWriter.append('I');
      }
  
      /**
       * private methods
       * @return
       */
  
      private PrintStream getWriter() {
          return fWriter;
      }
  
      protected void printHeader(long runTime) {
          getWriter().println();
          getWriter().println("Time: " + elapsedTimeAsString(runTime));
      }
  
      protected void printFailures(Result result) {
          List&lt;Failure&gt; failures= result.getFailures();
          if (failures.size() == 0)
              return;
          if (failures.size() == 1)
              getWriter().println("There was " + failures.size() + " failure:");
          else
              getWriter().println("There were " + failures.size() + " failures:");
          int i= 1;
          for (Failure each : failures)
              printFailure(each, "" + i++);
      }
  
      protected void printFailure(Failure each, String prefix) {
          getWriter().println(prefix + ") " + each.getTestHeader());
          getWriter().print(each.getTrace());
      }
  
      protected void printFooter(Result result) {
          if (result.wasSuccessful()) {
              getWriter().println();
              getWriter().println("\t&lt;/suites&gt;\n");
              getWriter().println("&lt;/result&gt;\n");
              getWriter().println(" (" + result.getRunCount() + " test" + (result.getRunCount() == 1 ? "" : "s") + ")");
  
          } else {
              getWriter().println();
              getWriter().println("FAILURES!!!");
              getWriter().println("Tests run: " + result.getRunCount() + ",  Failures: " + result.getFailureCount());
          }
          getWriter().println();
      }
  
      /**
       * Returns the formatted string of the elapsed time. Duplicated from
       * BaseTestRunner. Fix it.
       */
      protected String elapsedTimeAsString(long runTime) {
          return NumberFormat.getInstance().format((double) runTime / 1000);
      }
  }
  </code></pre>
  
  <p>UPDATE - Ant build file</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;project name="COTPlus" default="main" basedir="."&gt;
  &lt;property name="src.dir" location="src" /&gt;
  
  &lt;target name="test" &gt;
  &lt;junit printsummary="on" haltonfailure="false"&gt;
    &lt;formatter type="xml" /&gt;
    &lt;batchtest todir="/test-reports"&gt;
      &lt;fileset dir="${src.dir}" includes="**/ExampleTest.java"  /&gt;
    &lt;/batchtest&gt;
  &lt;/junit&gt;
  &lt;/target&gt;
  
  &lt;target name="main" depends="test"&gt;
          &lt;description&gt;Main target&lt;/description&gt;
          &lt;echo&gt;${src.dir}&lt;/echo&gt;
      &lt;/target&gt;
  
  &lt;/project&gt;
  </code></pre>