Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby unit tests: run some code after each failed test

Is there some clean and elegant way to execute my code right after a failed assert in ruby unit tests in Test::Unit, before teardown gets executed?

I am doing some automated GUI testing and would like to take a screenshot right after something failed.

like image 985
Saulius Žemaitaitis Avatar asked Mar 23 '12 12:03

Saulius Žemaitaitis


2 Answers

If you're on 1.9, don't use Test::Unit::TestCase as your base class. Subclass it and override #run_test to rescue, take the screenshot and reraise:

class MyAbstractTestCase < Test::Unit::TestCase
  def run_test( *args )
    super(*args)
  rescue
    snapshot()
    raise
  end
end

Alternatively, and I think this is actually the most terse way in, you can use a before_teardown hook:

class MyTestCase < Test::Unit::TestCase
  add_teardown_hook do |tc|
    screenshot() unless tc.passed?   
  end
end

This won't work on 1.8's test/unit, but will with the minitest in 1.9.

like image 116
regularfry Avatar answered Oct 05 '22 19:10

regularfry


Well you could extend Test::Unit::Assertions to do what you like, i do not think there is a built-in way to do this. Perhaps something like this (quick & dirty):

require 'test/unit'

module Test::Unit::Assertions
  def safe_assert(test, msg=nil)
    passed = msg.nil? ? assert(test) : assert(test,msg)
  ensure
    puts 'take screenshot' unless passed
  end
end

class MyTest < Test::Unit::TestCase
  def setup
    puts 'setup'
  end

  def teardown
    puts 'teardown'
  end

  def test_something
    safe_assert true

    puts 'before failing assert'
    safe_assert false, "message"
    puts 'after failing assert'
  end
end

output:

Loaded suite unittest
Started
setup
before failing assert
take screenshot
teardown
F
Finished in 0.001094 seconds.

  1) Failure:
test_something(MyTest) [unittest.rb:5]:
message

1 tests, 2 assertions, 1 failures, 0 errors, 0 skips

Test run options: --seed 58428

EDIT: you could actually pass the args to assert in a simpler way:

module Test::Unit::Assertions
  def safe_assert(*args)
    passed = assert(*args)
  ensure
    puts 'take screenshot' unless passed
  end 
end

also, you could wrap a standard assertin a begin-ensure-end block if you only need this functionality infrequently:

class MyTest < Test::Unit::TestCase
  def test_something
    safe_assert true

    puts 'before failing assert'
    begin
      passed = assert false, "message"
    ensure
      puts 'take screenshot' unless passed
    end
    puts 'after failing assert'
  end
end 

or you build a method that ensures a screenshot like in the following example. This actually seems like the cleanest way to me:

def screenshot_on_fail
  passed = yield
ensure
  puts 'take screenshot' unless passed
end

class MyTest < Test::Unit::TestCase
  def test_something_else
    screenshot_on_fail do
      assert true
    end

    screenshot_on_fail do
      assert false, 'message'
    end
  end
end
like image 33
Patrick Oscity Avatar answered Oct 05 '22 17:10

Patrick Oscity