Steve Merrick [mailto:Steve.Merrick / Marconi.com] wrote:

> Can someone explain to me the *details* of how to use
> Test::Unit, preferably an example showing the source code to 
> be tested, the test code, and any other glue or details 
> necessary to make it all work? I've never done this before, 
> and it isn't clear to me how you arrange your code and 
> test-code so that your tests can be run automatically, 
> quickly and often. Test harnesses I've used in the past 
> require considerable effort to get started, and to use. TIA.

Hmmm... big question. Let me see if I can give you a little piece of the
answer to start chewing on, and then you can figure out what pieces are
still missing.

First of all, I don't usually start with the code. Usually, I start with
the test. What do we want to do? How about a simple little class that
will wrap a string at some column width? The test seems simple enough:

<file name="tc_string_wrapper.rb">
require 'test/unit'

class TC_StringWrapper < Test::Unit::TestCase
	def test_wrap
		wrapper = StringWrapper.new
		assert_equal("This is a\nwrapped\nline.",
wrapper.wrap("This is a wrapped line.", 9), "The line should have been
wrapped to 9 columns")
	end
end
</file>

Things to note: first of all, you need to require 'test/unit'. Not only
does this get you the whole framework, it will also magically run your
tests for you when you give the file to the interpreter. Secondly, you
need a subclass of Test::Unit::TestCase to inherit all the framework
goodness. Third, you need an instance method within your TestCase that
starts with "test". Every such method will be run as an individual test
when the file is invoked. Finally, the real power is in the various
"assert" methods, such as the #assert_equal used here. To test
something, we assert that some statement is correct. If it isn't, we get
a big fat failure. If it is, then we know we're done.

OK, so now we have a test. Let's run it:

<run>
C:\projects-ruby\Sandbox>ruby tc_string_wrapper.rb
Loaded suite tc_string_wrapper
Started...
..
Error occurred in test_wrap(TC_StringWrapper): NameError: uninitialized
constant StringWrapper at TC
_StringWrapper
        tc_string_wrapper.rb:5:in `test_wrap'
        tc_string_wrapper.rb:4

Finished in 0.0 seconds.
1 runs, 0 assertions, 0 failures, 1 errors
</run>

Well, waddaya know, I didn't create StringWrapper yet. Notice that that
the framework caught the exception for me and gave me some nice
information about it. It's handy to not have an unexpected exception (is
that redundant?) bomb the interpreter and skip all the other tests.

This of course poses no real problem; here's something that will
eliminate the error (actually two of them; normally I'd probably slay
them one at a time, but I don't want this to drag on for too long):

<file name="string_wrapper.rb">
class StringWrapper
	def wrap(string, columns)
	end
end
</file>

Let me add the require to the test and see if I can get a real failure
now:

<file name="tc_string_wrapper.rb">
require 'test/unit'
require 'string_wrapper'

class TC_StringWrapper < Test::Unit::TestCase
	def test_wrap
		wrapper = StringWrapper.new
		assert_equal("This is a\nwrapped\nline.",
wrapper.wrap("This is a wrapped line.", 9), "The line should have been
wrapped to 9 columns")
	end
end
</file>

<run>
C:\projects-ruby\Sandbox>ruby tc_string_wrapper.rb
Loaded suite tc_string_wrapper
Started...
..
Failure occurred in test_wrap(TC_StringWrapper)
[tc_string_wrapper.rb:7]: The line should have been
wrapped to 9 columns. Expected <This is a
wrapped
line.> but was <nil>

Finished in 0.0 seconds.
1 runs, 1 assertions, 1 failures, 0 errors
</run>

Notice the helpful failure message. The string is not what was
expected... as a matter of fact, it's not even a string. This is a
possibility to make it pass:

<file name="string_wrapper.rb">
class StringWrapper
	def wrap(string, columns)
		string.scan(/(.{1,9}) /).join("\n")
	end
end
</file>

I have no earthly idea if that will work (really!), but the tests are
waiting to tell me:

<run>
C:\projects-ruby\Sandbox>ruby tc_string_wrapper.rb
Loaded suite tc_string_wrapper
Started...
..
Failure occurred in test_wrap(TC_StringWrapper)
[tc_string_wrapper.rb:7]: The line should have been
wrapped to 9 columns. Expected <This is a
wrapped
line.> but was <This is a
wrapped>

Finished in 0.0 seconds.
1 runs, 1 assertions, 1 failures, 0 errors
</run>

Oh, sorrow, oh tragedy, oh failure! I'm getting closer, but no
enchilada. No surprise either... I write my tests before I write my code
precisely because I'm not usually smart enough to get this stuff right
the first time. Maybe the second?:

<file name="string_wrapper.rb">
class StringWrapper
	def wrap(string, columns)
		string.scan(/(.{1,9})(?: |$)/).join("\n")
	end
end
</file>

<run>
C:\projects-ruby\Sandbox>ruby tc_string_wrapper.rb
Loaded suite tc_string_wrapper
Started...
..
Finished in 0.01 seconds.
1 runs, 1 assertions, 0 failures, 0 errors
</run>

Yippee! In testing nomenclature (although it doesn't quite map to the
console), I've got a green bar! All my tests pass. Of course, at this
point, they're trivial and have lots of holes, but that just means I
need more of them. If I were to continue, I'd keep adding things I
thought might fail, fixing them, and passing my tests. I'd repeat this
cycle until I was confident the method would stand up to my expectations
of it.

This is really a simple process... copying the information in to the
email took longer than writing and running both tests and code.

There's a lot more sugar in the framework (which you can read about at
http://testunit.talbott.ws/doc/), but I've shared the basics of what I
personally do with it. I could ramble on and on about testing (actually,
I already have: http://www.rubyconf.com/2001/talks/testinginreverse/),
but I don't want to overrun you with details. Let me know what questions
you still have, or if I've totally misinterpreted your original
question, and we'll go from there.

Others should feel free to correct me, chime in and/or make sense...


Nathaniel

<:((><
+ - -
| RoleModel Software, Inc.
| EQUIP VI