JUnit Formatter for Cucumber

May 12, 2009

UPDATE: the junit formatter is now included in cucumber, from version 0.3.4 onwards. Use it by cucumber --format junit --out path-to-report-dir features.

We’ve just jumped on the cucumber bandwagon here at Citysearch, and are happily churning out acceptance tests. Hudson is our continuous integration server of choice.

In order to get the nice junit report for our cucumber tests, I’ve coded up a dodgy JUnit formatter.

Download the file via the box.net widget on the main page (junit_formatter.rb), or cut-and-paste the source code below. We’ve put it in features/support, but as long as cucumber can find it you should be able to type:
cucumber --format Sensis::JUnitFormatter features to have it churn out similar XML files to the ones JUnit produces.

Limitations: currently it writes all files to ./build/report/TEST-.xml; all non-passing steps are treated as failures (even pending ones). Also, you should know that my Ruby skills are meagre and this code is supplied without warranty, etc. Don’t blame me (or Sensis, or Citysearch) if it deletes everything on your hard drive.

begin
  require 'builder'
rescue LoadError
  gem 'builder'
  require 'builder'
end

module Sensis

  class JUnitFormatter < Cucumber::Ast::Visitor
    def initialize(step_mother, io, options)
      super(step_mother)
    end

    def visit_feature(feature)
      @failures = @errors = @tests = 0
      @builder = Builder::XmlMarkup.new( :indent => 2 )
      super

      @testsuite = Builder::XmlMarkup.new( :indent => 2 )
      @testsuite.instruct!
      @testsuite.testsuite(
        :failures => @failures,
        :errors => @errors,
        :tests => @tests,
        :name => @feature_name ) do
          @testsuite << @builder.target!
      end

      #puts @testsuite.target!
      puts "Writing test output #{@feature_filename}"
      File.open(@feature_filename, 'w') { |file| file.write(@testsuite.target!) }
    end

    def visit_feature_name(name)
      lines = name.split(/\r?\n/)
      @feature_name = lines&#91;0&#93;.sub(/Feature\:/, '').strip
      puts "Beginning #{lines&#91;0&#93;}"
      @feature_filename = convert_to_file_name(@feature_name)
    end

    def visit_scenario_name(keyword, name, file_colon_line, source_indent)
      puts "Running #{keyword}:#{name}"
      @scenario = name
    end

    def visit_steps(steps)
      @steps_failed = false
      super
      @failures += 1 if @steps_failed
      @tests += 1
    end

    def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
      step_name = keyword + " " + step_match.format_args(lambda{|param| "*#{param}*"})
      @builder.testcase(:classname => "#{@feature_name}.#{@scenario}", :name => step_name) do
        if status != :passed
          @builder.failure(:message => step_name) do
            @builder.text!(format_exception(exception)) if exception
          end
          @steps_failed = true
        end
      end
    end    

    private

    def convert_to_file_name(value)
      "./build/report/TEST-" + value.gsub(/[^\w_\.]/, '_') + ".xml"
    end

     def format_exception(exception)
        (["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n")
      end
  end

end
Advertisements

7 Responses to “JUnit Formatter for Cucumber”


  1. […] Citysearch® Australia Code Monkeys Making Citysearch Work. And Sometimes Not Work. « JUnit Formatter for Cucumber […]

  2. Gregory Says:

    Thank you, thank you , thank you!
    Exactly what I was looking for.

  3. Gregory Says:

    The junit formatter included in cucumber didn’t work for me. Well, it does work but also adds a tag to all the testcases (which remains empty unless the test failed). It confuses Hudson which then considers that all the tests have failed.

    Your exporter works like a charm however.

    • csausdev Says:

      They should be the same – what version of cucumber are you using?

      • Gregory Says:

        Actually I went to the bottom of the problem and discovered I was getting the same results with both. But having your exporter helped a lot because I then realised that all the skipped tests were considered as failures (with an empty message).

        Although I agree they’re not successful tests and shouldn’t counted as such, I found it much convenient to limit the list to failed testcases. The enormous list of failures I had before was simply too big to be any useful (especially when you attempt to found the faulty steps).

        Any reason why the line 57 can’t be “if status == :failure” ?

  4. csausdev Says:

    If you just have “status == :failure” then the missing (unimplemented) steps don’t get counted as failed tests either. I was trying not to hide anything that wasn’t a pass.

    I guess it should be configurable as to which ones get flagged as failed tests. Feel free to fork the code, modify and then see if Aslak wants the modifications for the trunk.


  5. […] looked at some on the existing attempts at a junit formatter, but none where quite good enough, IMO. I needed duration times, and the […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: