Forum: Ruby test/spec 0.10, a BDD interface for Test::Unit

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
7264fb16beeea92b89bb42023738259d?d=identicon&s=25 Christian Neukirchen (Guest)
on 2009-02-01 12:30
(Received via mailing list)
Hello,

today I'm releasing test/spec 0.10, a library to do BDD with Test::Unit.

== News in 0.10:

* February 1st, 2009: Sixth public release 0.10.
  * Support for Ruby 1.9.  You must have the test-unit gem on 1.9.

== What is test/spec?

test/spec layers an RSpec-inspired interface on top of Test::Unit, so
you can mix TDD and BDD (Behavior-Driven Development).

test/spec is a clean-room implementation that maps most kinds of
Test::Unit assertions to a `should'-like syntax.

Consider this Test::Unit test case:

    class TestFoo < Test::Unit::TestCase
      def test_should_bar
        assert_equal 5, 2 + 3
      end
    end

In test/spec, it looks like this:

    require 'test/spec'

    context "Foo" do
      specify "should bar" do
        (2 + 3).should.equal 5
      end
    end

Since test/spec 0.4, you can also use the new RSpec 1.0 style:

    require 'test/spec'

    describe "Foo" do
      it "should bar" do
        (2 + 3).should.equal 5
      end
    end

test/spec does not include a mocking/stubbing-framework; use whichever
you like to.  test/spec has been tested successfully with FlexMock and
Mocha.

test/spec has no dependencies outside Ruby 1.8.

== Mixing test/spec and test/unit

test/spec and Test::Unit contexts/test cases can be intermixed freely,
run in the same test and live in the same files.  You can just add them
to your Rake::TestTask, too.  test/spec allows you to leverage your
full existing Test::Unit infrastructure.

test/spec does not change Test::Unit with the exception of
monkey-patching Test::Unit::TestSuite to order the test cases before
running them.  (This should not do any harm, but if you know a way
around it, please tell me.)

== Wrapped assertions

assert_equal: should.equal, should ==
assert_not_equal: should.not.equal, should.not ==
assert_same: should.be
assert_not_same: should.not.be
assert_nil: should.be.nil
assert_not_nil: should.not.be.nil

assert_in_delta: should.be.close
assert_match: should.match, should =~
assert_no_match: should.not.match, should.not =~

assert_instance_of: should.be.an.instance_of
assert_kind_of: should.be.a.kind_of
assert_respond_to: should.respond_to

assert_raise: should.raise
assert_nothing_raised: should.not.raise
assert_throws: should.throw
assert_nothing_thrown: should.not.throw
assert_block: should.satisfy

== Additional assertions

These assertions are not included in Test::Unit, but have been added
to test/spec for convenience:

* should.not.satisfy
* a.should.<predicate> (works like assert a.<predicate>?)
* a.should.be <operator> (where <operator> is <, <=, >, >=, or ===)
* should.output, to check what is printed

== Messaging/Blaming

With more complex assertions, it may be helpful to provide a message
to show if the assertion has failed.  This can be done with the
Should#blaming or Should#messaging methods:

    RUBY_VERSION.should.messaging("Ruby too old.").be > "1.8.4"

    (1 + 1).should.blaming("weird math").not.equal 11

== Custom shoulds ("Matchers")

To capture recurring patterns in parts of your specifications, you can
define custom "shoulds" (RSpec calls them "matchers") in your
contexts, or include modules of them:

    context "Numbers"
      class EqualString < Test::Spec::CustomShould
        def matches?(other)
          object == other.to_s
        end
      end

      def equal_string(str)
        EqualString.new(str)
      end

      specify "should have to_s"
        42.should equal_string("42")
      end
    end

Alternatively, your implementation can define
CustomShould#assumptions, where you can use test/spec assertions
instead of Boolean predicates:

    class EqualString < Test::Spec::CustomShould
      def assumptions(other)
        object.should.equal other.to_s
      end
    end

A CustomShould by default takes one argument, which is placed in
self.object for your convenience.

You can CustomShould#failure_message to provide a better error
message.

== SpecDox and RDox

test/spec adds two additional test runners to Test::Unit, based on the
console runner but with a different output format.

SpecDox, run with --runner=specdox (or -rs) looks like RSpec's output:

    should.output
    - works for print
    - works for puts
    - works with readline

RDox, run with --runner=rdox (or -rr) can be included for RDoc
documentation:

    == should.output
    * works for print
    * works for puts
    * works with readline

SpecDox and RDox work for Test::Unit too:

    $ ruby -r test/spec test/testunit/test_testresult.rb -rs

    Test::Unit::TC_TestResult
    - fault notification
    - passed?
    - result changed notification

    Finished in 0.106647 seconds.

    3 specifications (30 requirements), 0 failures

== Shared contexts

Since version 0.9, you can define shared contexts in test/spec using
shared_context/describe_shared.  These contexts are not executed on
their own, but can be included with it_should_behave_like/behaves_like
in other contexts.  You can use shared contexts to structure suites
with many recurring specifications.

== Thanks to

* Eero Saynatkari for writing should.output.
* Tuxie for writing test/spec on Rails.
* Brian Donovan for allowing alternative superclasses.
* Xavier Shay for implementing nested setups/teardowns.
* Chris Wanstrath for should.raise with a block and xcontext.
* Jean-Michel Garnier for packaging the first gem.
* Mikko Lehtonen, Jan Wikholm, Matt Mower and Michael Fellinger for
  testing the gem.
* Chris McGrath for reporting a bug.
* Thomas Fuchs for script.aculo.us BDD testing which convinced me.
* Dave Astels for BDD.
* The RSpec team for API inspiration.
* Nathaniel Talbott for Test::Unit.

== Copying

Copyright (C) 2006, 2007, 2008, 2009  Christian Neukirchen
<http://purl.org/net/chneukirchen>
test/spec is licensed under the same terms as Ruby itself.

== Where can I get it?

You can download test/spec 0.10 at

        http://chneukirchen.org/releases/test-spec-0.10.0.tar.gz

Alternatively, you can checkout from the development repository with:

           darcs get http://chneukirchen.org/repos/testspec

Please mail bugs, suggestions and patches to
<mailto:chneukirchen@gmail.com>.

(Patches using "darcs send" are most welcome.)

== Installing with RubyGems

Since version 0.3, a Gem of test/spec is available.  You can install
with:

    gem install test-spec

(It may take some time for the index to be updated and the mirrors
propagated.)  I also provide a local mirror of the gems (and
development snapshots) at my site:

    gem install test-spec --source
http://chneukirchen.org/releases/gems/

== Links

Behavior-Driven Development:: <http://behaviour-driven.org/>
RSpec:: <http://rspec.rubyforge.org/>
script.aculo.us testing::
<http://mir.aculo.us/articles/2006/08/29/bdd-style-...
FlexMock:: <http://onestepback.org/software/flexmock/>
Mocha:: <http://mocha.rubyforge.org/>


Happy hacking and have a nice day,
Christian Neukirchen

9519c8f4683268bb47c0fc6a3d6fe925d29fa52a  test-spec-0.10.0.tar.gz
ec7ea48707f247316a4556c3790d49e580c2e8c9  test-spec-0.10.0.gem
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-02-01 13:45
(Received via mailing list)
Christian Neukirchen wrote:

> In test/spec, it looks like this:
>
>     require 'test/spec'
>
>     context "Foo" do
>       specify "should bar" do
>         (2 + 3).should.equal 5
>       end
>     end

We use TextMate at work. (This may come as a surprise to those of you
who read
my posts, but I lack the political prowess to help us move to Aptana or
something with a better GUI...)

When we put a cursor inside a def test_case and claw <Shift+Splat+R>,
TextMate
finds the 'def test_case' above us, and runs ruby -n
file_name/test_case.

That gives us Incremental Testing during our Test-Driven Development
sessions.

We auditioned test/spec, and we have many specs still in producton, but
we found
that <Shift+Splat+R> did not always work. Sometimes it runs the right
spec, and
sometimes the wrong one...

I'm aware this is not your fault, but I remain curious about our
editors'
general abilities to help and not hinder TDD!

Now a question about spec architecture in general.

>
>       specify "should have to_s"
>         42.should equal_string("42")
>       end
>     end

As far as I can tell, the only aspect of any *spec system that is not
just a
remap of test_case architecture is the ability to run nested context{}
blocks
with incrementally nested setup{} blocks.

   context 'foo' do
     setup{ @foo = Foo.new }
     it('should see foo'){ @foo.should.not.be.nil }

     def increment_foo
       return @foo.x + 42
     end

     context 'foo' do
       setup{ @bar = Bar.new }
       it 'should see both foo & bar' do
         @foo.should.not.be.nil
         @bar.should.not.be.nil
       end
     end
   end

That is very important for projects with lots of business rules.

However, can the inner context see the method increment_foo() ?
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2009-02-01 17:42
(Received via mailing list)
On Feb 1, 2009, at 6:44 AM, Phlip wrote:

> We use TextMate at work.

> When we put a cursor inside a def test_case and claw <Shift+Splat
> +R>, TextMate finds the 'def test_case' above us, and runs ruby -n
> file_name/test_case.
>
> That gives us Incremental Testing during our Test-Driven Development
> sessions.
>
> We auditioned test/spec, and we have many specs still in producton,
> but we found that <Shift+Splat+R> did not always work. Sometimes it
> runs the right spec, and sometimes the wrong one...

Here are the regular expressions we use inside TextMate to find specs
from test/spec:

     # test/spec.
     spec      = $3 || $4 if lines.find { |line| line =~ /^\s*(specify|
it)\s+('(.*)'|"(.*)")+\s*(\{|do)/ }
     context   = $3 || $4 if lines.find { |line| line =~ /^\s*(context|
describe)\s+('(.*)'|"(.*)")+\s*(\{|do)/ }

If those need tweaking, let us know.

James Edward Gray II
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-02-01 21:50
(Received via mailing list)
>      context   = $3 || $4 if lines.find { |line| line =~ /^\s*(context|
> describe)\s+('(.*)'|"(.*)")+\s*(\{|do)/ }
>
> If those need tweaking, let us know.
>
> James Edward Gray II

Thanks! I forwarded them to The Usual Suspects!
This topic is locked and can not be replied to.