├── .rspec ├── spec ├── spec_helper.rb └── formatter_conformity_spec.rb ├── .rvmrc ├── Gemfile ├── lib ├── yarjuf │ ├── version.rb │ └── j_unit.rb └── yarjuf.rb ├── features ├── support │ └── env.rb ├── step_definitions │ ├── infrastructure_steps.rb │ ├── individual_suite_details_steps.rb │ ├── individual_tests_steps.rb │ └── suite_level_details_steps.rb ├── basic.feature ├── individual_tests.feature ├── individual_suites.feature └── suite_level_details.feature ├── .travis.yml ├── .gitignore ├── HISTORY.txt ├── Rakefile ├── yarjuf.gemspec ├── LICENSE.txt └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color --require spec_helper 2 | 3 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'yarjuf' 2 | 3 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm --create ruby-1.9.3-p194@yarjuf 2 | 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/yarjuf/version.rb: -------------------------------------------------------------------------------- 1 | module Yarjuf 2 | VERSION = '2.0.0' 3 | end 4 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | require 'aruba/cucumber' 5 | require 'nokogiri' 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.3 5 | - 2.0.0 6 | - 2.1.2 7 | gemfile: 8 | - Gemfile 9 | branches: 10 | only: 11 | - master 12 | -------------------------------------------------------------------------------- /lib/yarjuf.rb: -------------------------------------------------------------------------------- 1 | # external dependencies 2 | require 'time' 3 | require 'builder' 4 | require 'rspec/core' 5 | require 'rspec/core/formatters' 6 | 7 | # internal dependencies 8 | require 'yarjuf/j_unit' 9 | require 'yarjuf/version' 10 | -------------------------------------------------------------------------------- /features/step_definitions/infrastructure_steps.rb: -------------------------------------------------------------------------------- 1 | When /^I parse the junit results file$/ do 2 | results_file = 'tmp/aruba/results.xml' 3 | expect(File).to exist results_file 4 | @results ||= Nokogiri::XML File.open(results_file) 5 | end 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .idea 19 | -------------------------------------------------------------------------------- /HISTORY.txt: -------------------------------------------------------------------------------- 1 | 1.0 2 | - Initial release 3 | 4 | 1.0.1 5 | - Bug in RSpec 2.12 needs coding around 6 | 7 | 1.0.2 8 | - Added dependency on the 'builder' gem 9 | 10 | 1.0.3 11 | - Bug fix in RSpec 2.12.1 means we can go back to how it was before 1.0.1 12 | 13 | 1.0.4 14 | - No functional changes, just a big refactor to make the code more 15 | maintainable. 16 | 17 | 1.0.5 18 | - some refactoring, no functional changes 19 | 20 | 1.0.6 21 | - Last version that works with RSpec 2. Some internal refactoring. 22 | 23 | 2.0.0 24 | - Now works with rspec 3 (thanks @bsnape ) 25 | 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'cucumber/rake/task' 3 | require 'rspec/core/rake_task' 4 | 5 | namespace :cuke do 6 | Cucumber::Rake::Task.new(:all) do |t| 7 | t.cucumber_opts = "--format pretty" 8 | end 9 | 10 | Cucumber::Rake::Task.new(:wip) do |t| 11 | t.cucumber_opts = "--format pretty -t @wip" 12 | end 13 | end 14 | 15 | namespace :spec do 16 | RSpec::Core::RakeTask.new(:all) do |t| 17 | t.pattern = "spec/**/*_spec.rb" 18 | t.ruby_opts = "-I lib" 19 | end 20 | end 21 | 22 | task :default => ['spec:all', 'cuke:all'] 23 | 24 | -------------------------------------------------------------------------------- /spec/formatter_conformity_spec.rb: -------------------------------------------------------------------------------- 1 | describe JUnit do 2 | context "interface conformity" do 3 | subject { JUnit.new "some output" } 4 | 5 | it "should respond to example passed" do 6 | should respond_to :example_passed 7 | end 8 | 9 | it "should respond to example failed" do 10 | should respond_to :example_failed 11 | end 12 | 13 | it "should respond to example pending" do 14 | should respond_to :example_pending 15 | end 16 | 17 | it "should respond to dump summary" do 18 | should respond_to :dump_summary 19 | end 20 | end 21 | end 22 | 23 | -------------------------------------------------------------------------------- /features/basic.feature: -------------------------------------------------------------------------------- 1 | Feature: Basic use of Yarjuf 2 | As a tester 3 | In order to be able to get junit formatted results from rspec 4 | I want to be able to use yarjuf 5 | 6 | Background: 7 | Given a file named "spec/basic_spec.rb" with: 8 | """ 9 | describe "basic usage" do 10 | it "should work" do 11 | expect(true).to be true 12 | end 13 | end 14 | """ 15 | 16 | Scenario: Requiring Yarjuf 17 | When I run `rspec spec/basic_spec.rb -r ../../lib/yarjuf -f JUnit` 18 | Then the exit status should be 0 19 | 20 | Scenario: Writing output to a file 21 | When I run `rspec spec/basic_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 22 | Then the exit status should be 0 23 | And a file named "results.xml" should exist 24 | 25 | -------------------------------------------------------------------------------- /features/step_definitions/individual_suite_details_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the junit output file contains two testsuite elements named 'suite one' and 'suite two'$/ do 2 | step 'I parse the junit results file' 3 | expect(@results.xpath("/testsuites/testsuite").size).to eq 2 4 | expect(@results.xpath("/testsuites/testsuite/@name").map(&:text).sort).to match_array ["suite one", "suite two"] 5 | end 6 | 7 | Then /^the junit output file has one test against each suite$/ do 8 | step 'I parse the junit results file' 9 | expect(@results.xpath("/testsuites/testsuite/@tests").map(&:text)).to match_array ["1", "1"] 10 | end 11 | 12 | Then /^the junit output file has the correct test counts against each suite$/ do 13 | step 'I parse the junit results file' 14 | #suite one 15 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite one']/@tests").value).to eq "4" 16 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite one']/@failures").value).to eq "2" 17 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite one']/@skipped").value).to eq "1" 18 | 19 | #suite two 20 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite two']/@tests").value).to eq "6" 21 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite two']/@failures").value).to eq "1" 22 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite two']/@skipped").value).to eq "2" 23 | end 24 | 25 | -------------------------------------------------------------------------------- /yarjuf.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'yarjuf/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'yarjuf' 8 | gem.version = Yarjuf::VERSION 9 | gem.platform = Gem::Platform::RUBY 10 | gem.authors = ['Nat Ritmeyer', 'Ben Snape'] 11 | gem.email = ['nat@natontesting.com'] 12 | gem.homepage = 'http://github.com/natritmeyer/yarjuf' 13 | gem.summary = 'Yet Another RSpec JUnit Formatter (for Hudson/Jenkins)' 14 | gem.description = 'Yet Another RSpec JUnit Formatter (for Hudson/Jenkins)' 15 | 16 | gem.files = `git ls-files`.split($/) 17 | gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } 18 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 19 | gem.require_paths = ['lib'] 20 | 21 | gem.add_runtime_dependency('rspec', '~> 3') 22 | gem.add_runtime_dependency('builder') 23 | 24 | gem.add_development_dependency('nokogiri', '~> 1.5.10') # for Ruby 1.8.7 25 | gem.add_development_dependency('rake', '~> 10.3.2') 26 | gem.add_development_dependency('cucumber', '~> 1.3.16') 27 | gem.add_development_dependency('aruba', '~> 0.6.0') 28 | gem.add_development_dependency('simplecov', '~> 0.8.2') # for Ruby 1.8.7 29 | gem.add_development_dependency('reek', ['= 1.3.7']) # for Ruby 1.8.7 30 | gem.add_development_dependency('rainbow', '~> 1.99.2') # for Ruby 1.8.7 31 | end 32 | -------------------------------------------------------------------------------- /features/step_definitions/individual_tests_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the junit output file contains a test result with a simple name$/ do 2 | step 'I parse the junit results file' 3 | expect(@results.at_xpath("/testsuites/testsuite[@name='suite one']/testcase/@name").value).to eq "suite one simple name" 4 | end 5 | 6 | Then /^the junit output file has a nicely rendered nested test name$/ do 7 | step 'I parse the junit results file' 8 | expect(@results.at_xpath("/testsuites/testsuite/testcase/@name").value).to eq "something that is really deep should still be displayed nicely" 9 | end 10 | 11 | Then /^the junit output file contains a test with a duration$/ do 12 | step 'I parse the junit results file' 13 | expect(@results.at_xpath("/testsuites/testsuite/testcase/@time").value).to match /[.0-9]+/ 14 | end 15 | 16 | Then /^the junit output file contains a pending test$/ do 17 | step 'I parse the junit results file' 18 | expect(@results.at_xpath("/testsuites/testsuite/testcase/skipped")).to_not be_nil 19 | end 20 | 21 | Then /^the junit output file contains a failing test$/ do 22 | step 'I parse the junit results file' 23 | expect(@results.at_xpath("/testsuites/testsuite/testcase/failure")).to_not be_nil 24 | expect(@results.at_xpath("/testsuites/testsuite/testcase/failure/@message").value).to eq "failed suite one should be failing" 25 | expect(@results.at_xpath("/testsuites/testsuite/testcase/failure/@type").value).to eq "failed" 26 | expect(@results.at_xpath("/testsuites/testsuite/testcase/failure").text).to match /expected.*2.*got.*1.*using.*==.*spec\/failing_test_spec\.rb:3/m 27 | end 28 | 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Nathaniel Ritmeyer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name Nathaniel Ritmeyer nor the names of contributors to 15 | this software may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS 19 | IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 20 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR 22 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 25 | OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 27 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 28 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /features/step_definitions/suite_level_details_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the junit output contains the testsuite element$/ do 2 | step 'I parse the junit results file' 3 | expect(@results.xpath("/testsuites").size).to eq 1 4 | end 5 | 6 | Then /^the junit output reports one passing test$/ do 7 | step 'I parse the junit results file' 8 | expect(@results.at_xpath("/testsuites/@errors").value).to eq "0" 9 | expect(@results.at_xpath("/testsuites/@failures").value).to eq "0" 10 | expect(@results.at_xpath("/testsuites/@skipped").value).to eq "0" 11 | expect(@results.at_xpath("/testsuites/@tests").value).to eq "1" 12 | end 13 | 14 | Then /^the junit output reports one failing test$/ do 15 | step 'I parse the junit results file' 16 | expect(@results.at_xpath("/testsuites/@errors").value).to eq "0" 17 | expect(@results.at_xpath("/testsuites/@failures").value).to eq "1" 18 | expect(@results.at_xpath("/testsuites/@skipped").value).to eq "0" 19 | expect(@results.at_xpath("/testsuites/@tests").value).to eq "1" 20 | end 21 | 22 | Then /^the junit output reports one pending test$/ do 23 | step 'I parse the junit results file' 24 | expect(@results.at_xpath("/testsuites/@errors").value).to eq "0" 25 | expect(@results.at_xpath("/testsuites/@failures").value).to eq "0" 26 | expect(@results.at_xpath("/testsuites/@skipped").value).to eq "1" 27 | expect(@results.at_xpath("/testsuites/@tests").value).to eq "1" 28 | end 29 | 30 | Then /^the junit output testsuite element contains a duration$/ do 31 | step 'I parse the junit results file' 32 | expect(@results.at_xpath("/testsuites/@time").value).to match /[.0-9]+/ 33 | end 34 | 35 | Then /^the junit output testsuite element contains a timestamp$/ do 36 | step 'I parse the junit results file' 37 | expect(@results.at_xpath("/testsuites/@timestamp").value).to match /\d{4}-\d{2}-\d{2}T\d+:\d+:\d+\+\d+:\d+/ 38 | end 39 | 40 | -------------------------------------------------------------------------------- /features/individual_tests.feature: -------------------------------------------------------------------------------- 1 | Feature: Individual Tests 2 | As a tester 3 | In order to be able to see details of individual tests 4 | I want to write out test results 5 | 6 | Scenario: Simple test name 7 | Given a file named "spec/simple_test_name_spec.rb" with: 8 | """ 9 | describe "suite one" do 10 | it "simple name" do 11 | expect(1).to eq 1 12 | end 13 | end 14 | """ 15 | When I run `rspec spec/simple_test_name_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 16 | Then the junit output file contains a test result with a simple name 17 | 18 | Scenario: Nested tests 19 | Given a file named "spec/nested_spec.rb" with: 20 | """ 21 | describe "something" do 22 | context "that" do 23 | context "is" do 24 | context "really" do 25 | context "deep" do 26 | it "should still be displayed nicely" do 27 | expect(1).to eq 1 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | """ 35 | When I run `rspec spec/nested_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 36 | Then the junit output file has a nicely rendered nested test name 37 | 38 | Scenario: Test duration 39 | Given a file named "spec/test_duration_spec.rb" with: 40 | """ 41 | describe "suite one" do 42 | it "should do something" do 43 | expect(1).to eq 1 44 | end 45 | end 46 | """ 47 | When I run `rspec spec/test_duration_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 48 | Then the junit output file contains a test with a duration 49 | 50 | Scenario: Pending test 51 | Given a file named "spec/pending_test_spec.rb" with: 52 | """ 53 | describe "suite one" do 54 | it "should be pending" do 55 | pending 56 | fail 57 | end 58 | end 59 | """ 60 | When I run `rspec spec/pending_test_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 61 | Then the junit output file contains a pending test 62 | 63 | Scenario: Failing test 64 | Given a file named "spec/failing_test_spec.rb" with: 65 | """ 66 | describe "suite one" do 67 | it "should be failing" do 68 | expect(1).to eq 2 69 | end 70 | end 71 | """ 72 | When I run `rspec spec/failing_test_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 73 | Then the junit output file contains a failing test 74 | 75 | -------------------------------------------------------------------------------- /features/individual_suites.feature: -------------------------------------------------------------------------------- 1 | Feature: Individual suites 2 | As a tester 3 | In order to see relevant tests grouped together in suites 4 | I want to be able to differentiate between different `describe` blocks 5 | 6 | Scenario: Differentiating between 2 suites 7 | Given a file named "spec/suite_one_spec.rb" with: 8 | """ 9 | describe "suite one" do 10 | it "should contain one test" do 11 | expect(1).to eq 1 12 | end 13 | end 14 | """ 15 | And a file named "spec/suite_two_spec.rb" with: 16 | """ 17 | describe "suite two" do 18 | it "should also contain one test" do 19 | expect(1).to eq 1 20 | end 21 | end 22 | """ 23 | When I run `rspec spec/suite_one_spec.rb spec/suite_two_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 24 | Then the junit output file contains two testsuite elements named 'suite one' and 'suite two' 25 | And the junit output file has one test against each suite 26 | 27 | Scenario: Correct test result counts at individual suite level 28 | Given a file named "spec/suite_one_spec.rb" with: 29 | """ 30 | describe "suite one" do 31 | it "should contain one passing test" do 32 | expect(1).to eq 1 33 | end 34 | 35 | it "should contain one of two failing tests" do 36 | expect(1).to eq 2 37 | end 38 | 39 | it "should contain two of two failing tests" do 40 | expect(1).to eq 2 41 | end 42 | 43 | it "should contain 1 pending test" do 44 | pending 45 | fail 46 | end 47 | end 48 | """ 49 | And a file named "spec/suite_two_spec.rb" with: 50 | """ 51 | describe "suite two" do 52 | it "should contain one of 3 passing tests" do 53 | expect(1).to eq 1 54 | end 55 | 56 | it "should contain two of 3 passing tests" do 57 | expect(1).to eq 1 58 | end 59 | 60 | it "should contain three of 3 passing tests" do 61 | expect(1).to eq 1 62 | end 63 | 64 | it "should contain one failing test" do 65 | expect(1).to eq 2 66 | end 67 | 68 | it "should contain one of two pending tests" do 69 | pending 70 | fail 71 | end 72 | 73 | it "should contain two of two pending tests" do 74 | pending 75 | fail 76 | end 77 | end 78 | """ 79 | When I run `rspec spec/suite_one_spec.rb spec/suite_two_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 80 | Then the junit output file contains two testsuite elements named 'suite one' and 'suite two' 81 | And the junit output file has the correct test counts against each suite 82 | 83 | -------------------------------------------------------------------------------- /features/suite_level_details.feature: -------------------------------------------------------------------------------- 1 | Feature: Suite Summary 2 | As a tester 3 | In order to make the summary detail visible in jenkins 4 | I want to output the suite summary level detail to the output 5 | 6 | Scenario: testsuites element present 7 | Given a file named "spec/suite_element_spec.rb" with: 8 | """ 9 | describe "suite element" do 10 | it "should be present" do 11 | expect(1).to eq 1 12 | end 13 | end 14 | """ 15 | When I run `rspec spec/suite_element_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 16 | Then the junit output contains the testsuite element 17 | 18 | Scenario: One passing test 19 | Given a file named "spec/one_passing_test_spec.rb" with: 20 | """ 21 | describe "suite level details for 1 passing test" do 22 | it "should pass" do 23 | expect(1).to eq 1 24 | end 25 | end 26 | """ 27 | When I run `rspec spec/one_passing_test_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 28 | Then the junit output reports one passing test 29 | 30 | Scenario: One failing test 31 | Given a file named "spec/one_failing_test_spec.rb" with: 32 | """ 33 | describe "suite level details for 1 failing test" do 34 | it "should fail" do 35 | expect(1).to eq 2 36 | end 37 | end 38 | """ 39 | When I run `rspec spec/one_failing_test_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 40 | Then the junit output reports one failing test 41 | 42 | Scenario: One pending test 43 | Given a file named "spec/one_pending_test_spec.rb" with: 44 | """ 45 | describe "suite level details for 1 pending test" do 46 | it "should be pending" do 47 | pending 48 | fail 49 | end 50 | end 51 | """ 52 | When I run `rspec spec/one_pending_test_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 53 | Then the junit output reports one pending test 54 | 55 | Scenario: Test suite duration 56 | Given a file named "spec/suite_duration_spec.rb" with: 57 | """ 58 | describe "suite element duration" do 59 | it "should contain a duration" do 60 | expect(1).to eq 1 61 | end 62 | end 63 | """ 64 | When I run `rspec spec/suite_duration_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 65 | Then the junit output testsuite element contains a duration 66 | 67 | Scenario: Test suite time stamp 68 | Given a file named "spec/suite_timestamp_spec.rb" with: 69 | """ 70 | describe "suite element timestamp" do 71 | it "should contain a timestamp" do 72 | expect(1).to eq 1 73 | end 74 | end 75 | """ 76 | When I run `rspec spec/suite_timestamp_spec.rb -r ../../lib/yarjuf -f JUnit -o results.xml` 77 | Then the junit output testsuite element contains a timestamp 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yarjuf 2 | 3 | _Yet Another RSpec JUnit Formatter_ 4 | 5 | [![Build Status](https://travis-ci.org/natritmeyer/yarjuf.png)](https://travis-ci.org/natritmeyer/yarjuf) 6 | 7 | ## Intro 8 | 9 | I've never found a gem that can be relied on to generate JUnit 10 | output from [RSpec](https://www.relishapp.com/rspec/rspec-core/docs). Previously, I'd cobbled together a [formatter](http://www.natontesting.com/2012/05/25/rspec-junit-formatter-for-jenkins/) that worked for me for a couple of years and seems to have proved 11 | useful to others. But it was a hack and thought I'd rewrite it, make it 12 | conform to the JUnit format spec a bit better, and make it 13 | distributable as a gem. Thus: [yet-another-rspec-junit-formatter](https://github.com/natritmeyer/yarjuf) 14 | 15 | ## Installation 16 | 17 | Using rubygems: 18 | 19 | `gem install yarjuf` 20 | 21 | Using bundler: 22 | 23 | Add the following line to your `Gemfile`: 24 | 25 | `gem 'yarjuf'` 26 | 27 | ## Usage 28 | 29 | There are a few ways to use custom formatters in RSpec; what follows is 30 | the 'best' way... 31 | 32 | ### Loading yarjuf 33 | 34 | Before you can use yarjuf, RSpec needs to know about it. The best way to 35 | do that is to use the functionality that RSpec provides to load 36 | libraries. 37 | 38 | #### Modifying the `.rspec` file 39 | 40 | When RSpec executes, it looks for a file in the current working 41 | directory (project root) called `.rspec` that contains rspec 42 | configuration. It is a good idea to add the following to it: 43 | 44 | `--require spec_helper` 45 | 46 | Doing so will make sure that the `spec/spec_helper.rb` file will get 47 | required when RSpec starts. 48 | 49 | #### Modifying the `spec/spec_helper.rb` file 50 | 51 | Add the following to your `spec/spec_helper.rb`: 52 | 53 | ```ruby 54 | require 'yarjuf' 55 | ``` 56 | 57 | That will make sure that yarjuf is loaded when RSpec starts and can be 58 | used as a formatter. 59 | 60 | ### Generating JUnit output using yarjuf 61 | 62 | RSpec tests can be executed in a number of ways. Here's how to get JUnit 63 | output for each of those different ways - assuming you've loaded yarjuf 64 | as specified above). 65 | 66 | #### Running rspec tests from the command line 67 | 68 | In this scenario, you just want to run `rspec` from the command line and 69 | get JUnit output. To do that you'll need to use the `-f JUnit` option 70 | to generate JUnit output and to write it to a file you can use the 71 | `-o results.xml` option. So to run all your tests and get JUnit output 72 | written to a file, execute the following: 73 | 74 | `rspec -f JUnit -o results.xml` 75 | 76 | #### Running rspec tests using Rake 77 | 78 | In this scenario, you want to run your rspec tests using rake. To do 79 | that you'll need to add an option to your rake task: 80 | 81 | ```ruby 82 | RSpec::Core::RakeTask.new(:spec) do |t| 83 | t.rspec_opts = %w[-f JUnit -o results.xml] 84 | end 85 | ``` 86 | 87 | That will write out JUnit formatted results to a file called 88 | `results.xml`. 89 | 90 | #### Jenkins integration 91 | 92 | To use yarjuf with Jenkins(/Hudson), simply tick the 'Publish JUnit test 93 | result report' option in the Jenkins task configuration page and in the 94 | 'Test report XMLs' field specify the file name that you expect the JUnit 95 | formatted results to be written to, ie: the file path and name specified 96 | in the `-o` option above. 97 | 98 | ## Acknowledgements 99 | 100 | * Thanks to [@bsnape](https://github.com/bsnape) for the rspec 3 compatibility patch 101 | * Thanks to [@adeoke](https://github.com/adeoke) for suggesting a slightly less sucky gem name than the 102 | one I originally came up with 103 | * Thanks to [@dchamb84](https://github.com/dchamb84) for helping me debug the original hack 104 | * Thanks to [@nathanbain](https://github.com/nathanbain) for spurring me on to write the original hack 105 | 106 | -------------------------------------------------------------------------------- /lib/yarjuf/j_unit.rb: -------------------------------------------------------------------------------- 1 | # An RSpec formatter for generating results in JUnit format 2 | class JUnit 3 | RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :dump_summary 4 | 5 | def initialize(output) 6 | @output = output 7 | @test_suite_results = {} 8 | @builder = Builder::XmlMarkup.new :indent => 2 9 | end 10 | 11 | def example_passed(example_notification) 12 | add_to_test_suite_results example_notification 13 | end 14 | 15 | def example_failed(example_notification) 16 | add_to_test_suite_results example_notification 17 | end 18 | 19 | def example_pending(example_notification) 20 | add_to_test_suite_results example_notification 21 | end 22 | 23 | def dump_summary(summary) 24 | build_results(summary.duration, summary.examples.size, summary.failed_examples.size, summary.pending_examples.size) 25 | @output.puts @builder.target! 26 | end 27 | 28 | protected 29 | 30 | def add_to_test_suite_results(example_notification) 31 | suite_name = JUnit.root_group_name_for example_notification 32 | @test_suite_results[suite_name] = [] unless @test_suite_results.keys.include? suite_name 33 | @test_suite_results[suite_name] << example_notification.example 34 | end 35 | 36 | def failure_details_for(example) 37 | exception = example.exception 38 | formatted_backtrace = RSpec::Core::BacktraceFormatter.new.format_backtrace exception.backtrace 39 | exception.nil? ? '' : "#{exception.message}\n#{formatted_backtrace}" 40 | end 41 | 42 | # utility methods 43 | 44 | def self.count_in_suite_of_type(suite, test_case_result_type) 45 | suite.select { |example| example.metadata[:execution_result].status == test_case_result_type }.size 46 | end 47 | 48 | def self.root_group_name_for(example_notification) 49 | example_notification.example.metadata[:example_group][:description] 50 | end 51 | 52 | # methods to build the xml for test suites and individual tests 53 | 54 | def build_results(duration, example_count, failure_count, pending_count) 55 | @builder.instruct! :xml, :version => "1.0", :encoding => "UTF-8" 56 | @builder.testsuites( 57 | :errors => 0, 58 | :failures => failure_count, 59 | :skipped => pending_count, 60 | :tests => example_count, 61 | :time => duration, 62 | :timestamp => Time.now.iso8601) do 63 | build_all_suites 64 | end 65 | end 66 | 67 | def build_all_suites 68 | @test_suite_results.each do |suite_name, tests| 69 | build_test_suite(suite_name, tests) 70 | end 71 | end 72 | 73 | def build_test_suite(suite_name, tests) 74 | failure_count = JUnit.count_in_suite_of_type(tests, :failed) 75 | skipped_count = JUnit.count_in_suite_of_type(tests, :pending) 76 | 77 | @builder.testsuite( 78 | :name => suite_name, 79 | :tests => tests.size, 80 | :errors => 0, 81 | :failures => failure_count, 82 | :skipped => skipped_count) do 83 | @builder.properties 84 | build_all_tests tests 85 | end 86 | end 87 | 88 | def build_all_tests(tests) 89 | tests.each do |test| 90 | build_test test 91 | end 92 | end 93 | 94 | def build_test(test) 95 | test_name = test.metadata[:full_description] 96 | execution_time = test.metadata[:execution_result].run_time 97 | test_status = test.metadata[:execution_result].status 98 | 99 | @builder.testcase(:name => test_name, :time => execution_time) do 100 | case test_status 101 | when :pending 102 | @builder.skipped 103 | when :failed 104 | build_failed_test test 105 | end 106 | end 107 | end 108 | 109 | def build_failed_test(test) 110 | failure_message = "failed #{test.metadata[:full_description]}" 111 | 112 | @builder.failure(:message => failure_message, :type => 'failed') do 113 | @builder.cdata!(failure_details_for(test)) 114 | end 115 | end 116 | 117 | end 118 | --------------------------------------------------------------------------------