Asserts in aggregate objects called by a Test::Unit::TestCase

Howdy Rubyists,

I'm building a framework for automating tests so I've been sinking my
teeth into Test::Unit pretty heavily. Right now I'm trying to
automagically do a bunch of asserts in a sub object called by a
Test::Unit::TestCase subclass similar to something like this...

require "test/unit"

class TestStuff < Test::Unit::TestCase

   def test_stuff

      obj = SubAssertObj.new

      obj.verify_stuff("please_work")

   end

end

class SubAssertObj

    import Test::Unit::Assertions

    def verify_stuff(str)

       assert_equal("please_work", str)

    end

end

Now if the assert_equal fails it throws the
Test::Unit::AssertionFailedError like expected and it reports it very
nicely in the output. However if the assert is successful in the output
from Test::Unit I get

1 tests, 0 assertions, 0 failures, 0 errors

How can I get the assertions in the sub object to be reflected in the
report from Test::Unit?

Thanks,

Andrew

Howdy Rubyists,

I'm building a framework for automating tests so I've been sinking my
teeth into Test::Unit pretty heavily. Right now I'm trying to
automagically do a bunch of asserts in a sub object called by a
Test::Unit::TestCase subclass similar to something like this...

require "test/unit"

class TestStuff < Test::Unit::TestCase

   def test_stuff
      obj = SubAssertObj.new
      obj.verify_stuff("please_work")
   end

end

class SubAssertObj

    import Test::Unit::Assertions

    def verify_stuff(str)
       assert_equal("please_work", str)
    end

end

Now if the assert_equal fails it throws the
Test::Unit::AssertionFailedError like expected and it reports it very
nicely in the output. However if the assert is successful in the output
from Test::Unit I get

1 tests, 0 assertions, 0 failures, 0 errors

See add_assertion in Test::Unit::TestCase and Test::Unit::Assertions

How can I get the assertions in the sub object to be reflected in the
report from Test::Unit?

Why not write it like this:

class TestStuff < Test::Unit::TestCase

   def test_stuff
     util_verify_stuff 'please work'
   end

   def util_verify_stuff(str)
     assert_equal 'please work', str
   end

end

And then use subclassing to reuse common functionality?

···

On May 10, 2006, at 10:31 AM, Andrew Tanner wrote:

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

I finally figured out how to do this this week. This problem has been
nagging me for two years.

Sooner or later, when you build a Ruby testing framework of a certain
degree of complexity, it seems to stop counting your assertions. So
when you run the tests, you are incorrectly told you have no
assertions. This is really annoying and makes you look like an idiot.
What kind of tester doesn't put any verification in his tests? When
things are still working, it looks like this:

  Finished in 10.285 seconds.
  6 tests, 8 assertions, 0 failures, 0 errors

But then you get 0 assertions, even though you know your assertions are
actually being executed. Why? (And if you look closely, you'll see they
do get counted when they fail!)

The problem typically comes when you put assertions in your library
modules. This isn't hard. All you have to do is put include
Test::Unit::Assertions in your module or helper class and then you can
include calls to assert, assert_equal and all the other assert methods.
It works, except that your assertions aren't counted.

The reason why is that the method add_assertion does the counting. This
is automatically called by all the assert methods. When they call the
add_assertion method of TestCase, the count shows up in your test
results no problem. But the add_assertion method of the Assertions
module is just a no op. This is why they don't affect the assertion
count. The solution is to add a functional add_assertion method back to
your helper module/class that ties back to your test case.

Here's how. The first thing is to keep track of the test case that is
running. We'll just put it in a global variable, which is ugly, but it
works. The best place to put this is in the setup method of your test
case.

  def setup
    $testcase = self
  end

The test case is actually the object the setup method is part of, so
self gives us a reference to it when it runs. You may already have
other stuff in your setup method as well. That's fine; just add this
too.

Now we need to make a change to your helper class/module where you did
the include Test::Unit::Assertions. Add this method:

  def add_assertion
    $testcase.add_assertion
  end

The name for what we are doing here is "delegation," except that we are
doing it in a totally non-object-oriented way. We are "delegating" the
call to add_assertion back to the test case where it belongs. After
you've made these changes, tell your developer friends that you fixed
the assertion count problem by delegating the call to add_assertion
back to the test case. They'll be impressed.

But don't start bragging yet. If you run your code right now, you'll
get a complaint that you are calling a private method. I've seen some
clever hacks to get around this problem, but actually Ruby makes the
solution really easy. Just make the method public! Add this code to
your TestCase class definition:

  public :add_assertion

Try doing that in Java! Don't put in any of the methods; just put it
inside the class definition. To recap, your test case should now look
something like this:

  class MyTest < Test::Unit::TestCase
    def setup
      $testcase = self
      # other stuff maybe
    end
    public :add_assertion
    def test_something
      # your actual tests...

Interesting idea. Here is another possibility:

require 'test/unit'

module TestHelper
  include Test::Unit::Assertions

  def initialize(test)
    @test = test
  end

  def add_assertion
    @test.send(:add_assertion)
  end
end

class MyTestHelper
  include TestHelper

  def test_x
    assert(true)
  end
end

class TC_Delegate < Test::Unit::TestCase
  def test_delegating
    MyTestHelper.new(self).test_x
  end
end
__END__

Using send gets around the private problem (though that isn't
future-proof.) I think passing in the test is better than using a
global. Plus if you just include the TestHelper module to make use of
this, which results in less duplicate code than your solution. There
is probably an even slicker way to do this...

Ryan

···

On 5/11/06, bpettichord@gmail.com <bpettichord@gmail.com> wrote:

I finally figured out how to do this this week. This problem has been
nagging me for two years.

Sooner or later, when you build a Ruby testing framework of a certain
degree of complexity, it seems to stop counting your assertions. So
when you run the tests, you are incorrectly told you have no
assertions. This is really annoying and makes you look like an idiot.
What kind of tester doesn't put any verification in his tests? When
things are still working, it looks like this:

  Finished in 10.285 seconds.
  6 tests, 8 assertions, 0 failures, 0 errors

But then you get 0 assertions, even though you know your assertions are
actually being executed. Why? (And if you look closely, you'll see they
do get counted when they fail!)

The problem typically comes when you put assertions in your library
modules. This isn't hard. All you have to do is put include
Test::Unit::Assertions in your module or helper class and then you can
include calls to assert, assert_equal and all the other assert methods.
It works, except that your assertions aren't counted.

The reason why is that the method add_assertion does the counting. This
is automatically called by all the assert methods. When they call the
add_assertion method of TestCase, the count shows up in your test
results no problem. But the add_assertion method of the Assertions
module is just a no op. This is why they don't affect the assertion
count. The solution is to add a functional add_assertion method back to
your helper module/class that ties back to your test case.

Here's how. The first thing is to keep track of the test case that is
running. We'll just put it in a global variable, which is ugly, but it
works. The best place to put this is in the setup method of your test
case.

  def setup
    $testcase = self
  end

The test case is actually the object the setup method is part of, so
self gives us a reference to it when it runs. You may already have
other stuff in your setup method as well. That's fine; just add this
too.

Now we need to make a change to your helper class/module where you did
the include Test::Unit::Assertions. Add this method:

  def add_assertion
    $testcase.add_assertion
  end

The name for what we are doing here is "delegation," except that we are
doing it in a totally non-object-oriented way. We are "delegating" the
call to add_assertion back to the test case where it belongs. After
you've made these changes, tell your developer friends that you fixed
the assertion count problem by delegating the call to add_assertion
back to the test case. They'll be impressed.

But don't start bragging yet. If you run your code right now, you'll
get a complaint that you are calling a private method. I've seen some
clever hacks to get around this problem, but actually Ruby makes the
solution really easy. Just make the method public! Add this code to
your TestCase class definition:

  public :add_assertion

Try doing that in Java! Don't put in any of the methods; just put it
inside the class definition. To recap, your test case should now look
something like this:

  class MyTest < Test::Unit::TestCase
    def setup
      $testcase = self
      # other stuff maybe
    end
    public :add_assertion
    def test_something
      # your actual tests...

I've never needed to have all this complexity to reuse test methods.

Making abstract Test::Unit::TestCase subclasses is much simpler than dispatching to some module.

···

On May 11, 2006, at 1:13 PM, Ryan Leavengood wrote:

On 5/11/06, bpettichord@gmail.com <bpettichord@gmail.com> wrote:

To recap, your test case should now look
something like this:

  class MyTest < Test::Unit::TestCase
    def setup
      $testcase = self
      # other stuff maybe
    end
    public :add_assertion
    def test_something
      # your actual tests...

Interesting idea. Here is another possibility:

require 'test/unit'

module TestHelper
include Test::Unit::Assertions

def initialize(test)
   @test = test
end

def add_assertion
   @test.send(:add_assertion)
end
end

class MyTestHelper
include TestHelper

def test_x
   assert(true)
end
end

class TC_Delegate < Test::Unit::TestCase
def test_delegating
   MyTestHelper.new(self).test_x
end
end
__END__

Using send gets around the private problem (though that isn't
future-proof.) I think passing in the test is better than using a
global. Plus if you just include the TestHelper module to make use of
this, which results in less duplicate code than your solution. There
is probably an even slicker way to do this...

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com