A Ruby FITting, with Little Finesse
You Keep on Downloading, and Downloading, and Downloading ...
On my machine, I began by downloading the current version of FitNesse from fitnesse.org. The version was 20060719. I unzipped that into my C:\fitnesse folder and tried launching. What came up looked like FitNesse, until we compared it to Chet’s. Mine was some older version. I’m still not sure what happened. We copied Chet’s fitnesse folder over the top of mine, and then it didn’t work at all. It seemed to be whining about something in Java. We knew that it needed Java 5 (apparently the cleverly-named version of Java whose number is 1.5.x.) I’m running an older version of Java, for compatibility with my web building software, lest I have to rework it. But we needed Java 5 for FitNesse. Searching my computer we found that I have JDK 1.5.something. So we changed the run.bat for FitNesse from
java -cp fitnesse.jar fitnesse.FitNesse %1 %2 %3 %4 %5 pause
to
"c:\Program Files\Java\jdk1.5.0_03\jre\bin\java" -cp fitnesse.jar fitnesse.FitNesse -p 8088 %1 %2 %3 %4 %5 pause
It took a couple of tries, because all that first bit has to be in quotes, because Microsoft, in their wisdom, puts all the programs under a folder name that their own program, DOS, can’t properly understand. I had something on port 80, so we used 8088. When we did this, the FitNesse server comes up.
In text:
C:\fitnesse>"c:\Program Files\Java\jdk1.5.0_03\jre\bin\java" -cp fitnesse.jar ftnesse.FitNesse -p 8088 FitNesse (20060719) Started... port: 8088 root page: fitnesse.wiki.FileSystemPage at ./FitNesseRoot logger: none authenticator: fitnesse.authentication.PromiscuousAuthenticator html page factory: fitnesse.html.HtmlPageFactory page version expiration set to 14 days.
Whee! Went into my browser, to localhost:8088, and got the front page of FitNesse. I didn’t have the Ruby stuff yet, was waiting for Chet for that. I ran the FitNesse tests, and most of them ran, but a few, having to do with Virtual something, didn’t. A note from Chet confirmed that his was the same, so we decided to ignore it. Keep ‘em all green, Uncle Bob, that’s my advice.
Gem is a Gem
To download the fit stuff for Ruby, you go to a command prompt and type “gem install fit” (or maybe it’s fit-1.1, I forget). Either way, gem is a gem. It looks on your hard drive, discovers you don’t have fit. Looks at the directory of what’s out on the web, discovers you’re out of date. Downloads the current directory, then goes and gets fit-1.1 and installs it just where it is supposed to go. Wow, perfect.
Now we just had to make it work.
Day Two, More or Less
Today, at the Brighton Borders, after an Iced Chai Latte to get me going, we hooked up our machines via VNC, and got to work. Chet had Ruby/FitNesse working, but we wanted to do it over again so that we could report, as well as possible, on what it took.
The FitNesse home page has a table of interesting things on it, and Chet had added a row pointing to RubyFitnesse and RubyFitNesseTests.
I picked that version of the home page up when we copied Chet’s files onto my system, so we didn’t have to type it in. In any case, the wiki source for the page now looks like this on my system:
!img-l https://files/images/FitNesseLogoMedium.jpg !1 Welcome to [[FitNesse][FitNesse.FitNesse]]! !3 ''The fully integrated standalone acceptance testing framework and wiki.'' |!c '''Table of Contents'''| |!c [[A One-Minute Description][FitNesse.OneMinuteDescription]]|''What is [[FitNesse][FitNesse.FitNesse]]? Start here.''| |!c [[A Two-Minute Example][FitNesse.TwoMinuteExample]]|''A brief example. Read this one next.''| |!c [[User Guide][FitNesse.UserGuide]]|''Answer the rest of your questions here.''| |!c [[Acceptance Tests][FitNesse.SuiteAcceptanceTests]]|''FitNesse's suite of Acceptance Tests''| |!c [[Payroll System Tests][FitNesse.PayrollSystemSuite]]|Suite of Acceptance Tests for the Payroll System Exercise| |!c [[Ruby Fitnesse][FitNesse.RubyTests]]|Ruby FitNesse Tests|
The page pointed to, FitNesse.RubyTests, looks like this:
And the wiki source is:
!define COMMAND_PATTERN {C:/Program Files/ruby/bin/ruby.exe -I %p -I "C:/Program Files/ruby/lib/ruby/gems/1.8/gems/fit-1.1/lib" "C:/Program Files/ruby/lib/ruby/gems/1.8/gems/fit-1.1/bin/FitServer.rb" -v} !path C:\Program Files\ruby\lib\ruby\gems\1.8\gems\fit-1.1\lib\eg I have built a test to exercise the example ^ArithmeticFixture. ---- Next I would like to do some simple tests to exercise the column fixture. These tests will use fixtures and model code that will be stored in the c:\data\ruby directory. ^ChetTests
Notable about these lines is that I had to put the paths from my system in there, as highlighted. We also had to fiddle with where the quotes had to be and where they didn’t. The rule is simple enough … if it’ll break some program’s line-end parsing, it needs quotes. In practice, what’s there works. We think there is some gimmickry with underbars and dashes that could be used to make things “simpler”. Maybe we’ll come back to that later.
Now what is supposed to happen with that define and the path statement is that all pages beneath this one will use that information to run their tests. So the ArithmeticFixture should work now.
Let’s look at its wiki source:
!|eg.ArithmeticFixture| |x|y|+|-|*|/| |1|1|2|0|1|1|
Not much to that. And Voila! It does work!
Chet's Test
The ArithmeticFixture runs because of code that is built into the Ruby fit installation. We need to learn how to do our own. Originally, Chet’s page looked like this:
And the source was just this:
!path C:\Documents and Settings\Ron\My Documents\Data\Ruby Use a very simple fixture to make sure things work as I think they will. !|Fitnesse1.Division| |numerator|denominator| quotient()| |2|1|2| |60|30|2|
To make this link up and run, we have to learn some tricks. First, note that the name of the fixture is Fitnesse1.Division. If you’ll look at the path we show above, it comes down to Data\Ruby. Underneath that folder is the Ruby project I’m building, in a folder called Fitnesse1. That name is the same as the first name in the fixture. The fixture code itself looks like this:
require 'fit/column_fixture' module Fitnesse1 class Division < Fit::ColumnFixture attr_accessor :numerator, :denominator def quotient() @numerator.to_f / @denominator.to_f end end end
What I’m about to tell you is superstition: we have made it work this way, and we’re not sure how much flexibility we have. Maybe we’ll experiment later on. For now, what we think we know is that the module name, Fitnesse1, has to match the folder name, and the modifying name in the wiki page. The class name, Division, has to match the fixture name in the wiki page. Since that test shows inputs numerator and denominator, Chet coded them as member variables with accessors. The quotient method just returns the result of floating the two variables and dividing.
Again, there may be more flexibility than that. This is “cargo cult” programming right now, we just managed to make it work and we aren’t yet sure how much room there is to change things. But it does work:
Running Because We Can Walk
Up until now, we were just parroting what Chet had already figured out on his own. We were keen to move on. So we thought we’d try something with a row fixture.
I won’t go into the row fixture in great detail, but the basic idea is that a row fixture compares a collection in the fixture with a collection it gets from the system, and indicates what’s right and what’s not as right as it might be. We thought we’d do a little personnel database, as we often do. We added some code to Chet’s page:
!path C:\Documents and Settings\Ron\My Documents\Data\Ruby
Use a very simple fixture to make sure things work as I think they will.
!|Fitnesse1.Division|
|numerator|denominator| quotient()|
|2|1|2|
|60|30|2|
!|Fitnesse1.People|
|lastname|firstname|
|Hendrickson|Chet|
|Jefferies|Ron|
That makes it display like this:
Now aficionados of how I spell my name will recognize the extra e in the middle. It was our intention to make this test fail. We’ll show the code in a moment, but first let’s see it failing:
Sure enough, the test fails as we intended. (I didn’t think of myself as “surplus”, but there you have it.) Now let’s look at the Ruby code to make that happen. It’s pretty simple. In a file called people.rb, we have:
require 'fit/row_fixture' module Fitnesse1 class People < Fit::RowFixture def query p1 = Person.new("Chet", "Hendrickson") p2 = Person.new("Ron", "Jeffries") [p1, p2] end end class Person attr_accessor :lastname, :firstname def initialize(firstname, lastname) @lastname = lastname @firstname = firstname end end end
Pretty straightforward. The fixture is a row fixture, and it’s named People, in the module named Fitnesse1. It implements a method called query, which is another little bit of cargo cult. Row fixtures will be sent the method “query”, to which they must return the collection of objects to be checked in the fixture.
In our case, we create two Persons (more about them in a moment) and return them in an array. The Person class has two accessors, lastname and firstname, which match the column names in the test. And when we create a person, he or she is initialized in the obvious way.
All this goes together so that the query method on People returns an array of two people, Chet and Ron, with Ron’s name spelled correctly. (Please make a note of that.) The test fails, and we discover the problem and change the code in the Fitnesse wiki:
!|Fitnesse1.People|
|lastname|firstname|
|Hendrickson|Chet|
|Jeffries|Ron|
And the test now runs!!
Let me explain. No, there is too much. Let me sum up.
What we have here is a fairly minimal working example of Ruby and FitNesse cooperating. So far we’re in cargo cult mode: it’s working but we’re not experts by a long shot. There were a lot of things we had to get right, and we’re not sure how delicate they are:
- Download FitNesse from fitnesse.org and get it correctly installed.
- Start the FitNesse server using Java 5. You might need to download this: I had a copy lying around.
- Go to a suitable FitNesse page and build a link to your Ruby test page.
- Set up the command information on your top level Ruby page. This includes a command pattern, and a classpath. From the look of the paths on the page, my intuition is telling me that there's more flexibility there than we used. We'll wiggle around a bit in a future article, perhaps.
- The FitNesse fixture gets a module name (optional, it seems, since the ArithmeticFixture example doesn't have one. (Note that its path points to ruby\eq, not to the ruby folder. That's a hint at one kind of option.)
- The module name appears to need to be in a module statement in ruby, and the class code needs to be in a folder of that same name.
- The fixture name is the name of the file in that folder (.rb, of course), and the class name equals the fixture name.
- The row fixture calls a method named "query" to get a collection of objects.
- The objects in the collection need to respond to the method names used in the column of the row fixture.
Whew! That’s a lot to cover in a couple of hours. Probably the Iced Chai Latte helped. What’s missing? Well, we don’t know, but here are some thoughts:
- We haven't built any real model code. Right now, our fixtures are doing the work. Frankly, I like that fact. When I've seen teams using Fit or FitNesse, they often have built some object or objects, and start trying to build a fixture that interfaces to those objects. It can take a long time before the test runs. Working as we did, it's a lot like the "fake it till you make it" practice in Test-Driven Development. Our test runs ... it's just not talking to many real objects yet.
- We need to learn more about fixtures. There aren't many fixtures available for Ruby Fit / FitNesse. If we get smarter than we are right now, maybe we can get involved in trying to create some. In any case, we need to learn what's there and how to use them.
- We should take a look at how to organize tests and what to do with them. That will require us to figure out some little kind of application.
- ... and probably much much more ...
The bottom line, though, is that with a couple of hours work, some of it very frustrating for Chet as he went through that phase where you type random things in, hoping one of them will work, we have Ruby and FitNesse talking with each other. We’re on the way.
We hope this article will help in two ways. First, we hope it’ll get you interested in FitNesse and/or Ruby if you aren’t already. Second, we hope it’ll help you get started with a little less trouble than you’d otherwise have.
With that in mind, if you do play with Ruby/FitNesse, and you have any feedback for this article, please drop me an email. As always, include the string [ron] as part of the subject to be sure of getting through my spam filters.
Thank you, and good night!
Now! New Explanations! Exciting New Graphics!
Donald Roby kindly pointed out that the reason the virtual tests fail in the Fitnesse suite is that we didn’t have a variable set correctly. When you change what port Fitnesse opens on, you need to change an entry on the FitNesse.SuiteAcceptanceTests page. It’s the port setting. For my machine, running on 8080, it should say:
!2 ''Port''
!define PORT {8088}
!define INTERNAL_PORT {9123}
Making this change fixes all the virtual tests, though I had to run the tests twice before one of the errors went away. I’m still left with the comment fixture not working, as is Chet. A noticeable improvement!
Also, I decided to put a Fitnesse starter icon on my desktop. Rather than let it have the standard DOS box icon, I made a Fitnesse icon. I just did the 32x32 size, not the three or four sizes you’re supposed to have for an official M$ icon. Anyway, here it is, download it if you care to: