Some FitNesse tricks: Classpath and debugging
On my project, we use Maven to build our software and FitNesse to write functional specifications. However, it was obvious that FitNesse wasn’t designed by Maven-fans. When I use Maven, I already have control over my classpath, and specifying it in every FitNesse test gets to be old really fast. Why can’t I just inherit the project class path, and start FitNesse using maven-antrun-plugin or just from my IDE?
I found a neat way to implement this by overriding FitNesse. Using the same technique, I’m also able to debug FitNesse tests.
Taking control of FitNesse
Fixing the classpath is exceedingly simple. But you need to understand a bit of the inner workings of FitNesse to get it to work. When you press the “test” button on a test, or the suite button on a test suite, FitNesse instantiates a class based on the registered “responder”. Our first order of business is to override this, but in order to do that, we need to take control over FitNesse. You can do this by adding fitnesse.jar and fitlibrary.jar to your classpath, either manually in Eclipse, or by using a Maven dependency on org.fitnesse:fitnesse and org.fitnesse:fitlibrary. The latest version in the Maven repository is 20060719, which is old, but I’ve seen nothing that was worth the hassle of the manual download. Once FitNesse is in you IDE’s classpath, you can run the Java class “fitnesse.FitNesse”. This starts FitNesse on port 80. Navigate to, say “http://localhost:8080/MyFirstTest”, and you’re ready to add a test. In order to get it to work, though, you will have to do something like the following:
!path target/classes
!path /.m2/repository/org/fitnesse/fitnesse/20060719/fitnesse-20060719.jar
!path /.m2/repository/org/fitnesse/fitlibrary/20060719/fitlibrary-20060719.jar
!|my.test.ExampleFixture|
|first|second|sum?|product?|
|10|10|20|100|
|1|0|1|0|
|10000|1|10001|10000|
Ugh! Bad FitNesse!
Taking control of the classpath
After examining the class fitnesse.FitNesse, you will find that there is no simple way to modify the responder. So I basically copied that class into my own main class:
public class RunFitnesse {
public static void main(String args[]) throws Exception {
RunFitnesse runFitnesse = new RunFitnesse();
runFitnesse.start();
}
public void start() throws Exception {
FitNesseContext context = loadContext();
FitNesse fitnesse = new FitNesse(context);
fitnesse.applyUpdates();
boolean started = fitnesse.start();
if(started)
printStartMessage(context);
}
protected FitNesseContext loadContext() throws Exception {
FitNesseContext context = new FitNesseContext();
ComponentFactory componentFactory =
new ComponentFactory(context.rootPath);
context.port = 80;
context.rootPath = "./src/main/fitnesse";
context.rootPageName = "FitNesseRoot";;
context.rootPagePath = context.rootPath + "/" + context.rootPageName;
context.root = componentFactory.getRootPage(
FileSystemPage.makeRoot(context.rootPath, context.rootPageName));
context.responderFactory = new ResponderFactory(context.rootPagePath);
context.logger = null;
context.authenticator =
componentFactory.getAuthenticator(new PromiscuousAuthenticator());
context.htmlPageFactory =
componentFactory.getHtmlPageFactory(new HtmlPageFactory());
context.responderFactory.addResponder("test",
InheritClasspathTestResponder.class);
String extraOutput =
componentFactory.loadResponderPlugins(context.responderFactory);
extraOutput += componentFactory.loadWikiWidgetPlugins();
extraOutput += componentFactory.loadContentFilter();
return context;
}
}
Again: FitNesse really should provide better hooks for this! The cool part is of course InheritClasspathTestResponder
, which overrides the standard FitNesse TestResponder:
public class InheritClasspathTestResponder extends TestResponder {
@Override
protected String buildCommand(PageData data, String program,
String classPath) throws Exception {
String parentClassPath = System.getProperty("java.class.path");
String pathSeparator = System.getProperty("path.separator");
String[] classPathElements = parentClassPath.split(pathSeparator);
for (String element : classPathElements) {
classPath += pathSeparator + "\\"" + element + "\\"";
}
return super.buildCommand(data, program, classPath);
}
}
Running your new main class instead of fitnesse.FitNesse will add the current class path to the executing FitNesse class path. This means an end to monkeying around with FitNesse classpath. In order for suites to work, you also have to override SuiteResponder identically to TestResponder.
Debugging FitNesse
Add the following line after registering the test responder:
context.responderFactory.addResponder("debug", DebugTestResponder.class);
Here is DebugTestResponder:
public class DebugTestResponder extends TestResponder {
private static final int DEBUG\_PORT = 1044;
@Override
protected String buildCommand(PageData data, String program,
String classPath) throws Exception {
String parentClassPath = System.getProperty("java.class.path");
String pathSeparator = System.getProperty("path.separator");
String[] classPathElements = parentClassPath.split(pathSeparator);
for (String element : classPathElements) {
classPath += pathSeparator + "\\"" + element + "\\"";
}
String debugOptions = "-Xrunjdwp:transport=dt\_socket,server=y,suspend=y,address="
+ DEBUG\_PORT;
return "java " + debugOptions + " " + "-cp " + classPath + " "
+ program;
}
}
To use it, run the test in FitNesse as usual, but then replace the “?test” part with “?debug”. FitNesse will now start running, but then freeze, while it is waiting for a debugger to connect. Connect a remote debugger from your IDE to the debug port (1044) in my example. In Eclipse, this is done by selecting Run -> Open Debug Dialog, right click “Remote Java Application” and choose new.
In conclusion
FitNesse has a powerful mechanism for overriding the default behavior built in to the ResponderFactory framework. It is a bit hard to stuff things into the framework until you learn how to do it. I demonstrated how by replacing the “test” and “suite” verbs, we can address one of the most common complaint I get from FitNesse users: Class path struggles. But adding a “debug” verb, we can make a test suspend at start up while waiting for us to attach a remote debugger. Both these techniques have been huge time savers for me. I hope you like them, too.
Comments:
Johannes Brodwall - Mar 22, 2008
Hi, Ole Morten
Three reasons why I don’t like solutions like the fitnesse-pom-widget:
- You have to install fitnesse yourself. PLUS: You have to micky around with the installation. For me, maven is my only installer 2. You have to start up stuff with special commands and stuff. For me, Eclipse is my only application runner. 3. You still have to specify a path to the pom that will vary from workstation to workstation in your tests. For me, there is no changes when you relocate the workspace.
Close, but no cigar.
The debugger is nice, but the result of running the test won’t be displayed in your web browser, right?
[Ole Morten Amundsen] - Mar 22, 2008
Finally, I might assist you, Johannes! Nice work, but sadly, I think you’re reinventing the weel here. I meant to write a blog post about this, including how to use the pom. No excuses!
We include the pom in the Fitnesse page by writing “!pom path_to_the_pom_he_fixtures_were_built_from/pom.xml”. See http://boss.bekk.no/fitnesse-pom-widget/usage.html
Now you may set up a debugger in eclipse. Java Application->new-> Main class: fitnesse.runner.TestRunner Arguments: localhost 8091 YourWikiPathToTestOrSuite -v -html fitnesse.html
Bonus tip to your many readers: add “-e 0” to the startup of your FitNesse server to avoid those dreadful zips. Isn’t FitNesse intuitive?
Johannes Brodwall - Mar 27, 2008
Hei, Nils-Helge
Using plain old Fit is indeed an option that I considered, too. But now I am actually glad I didn’t. I hope you’ll like to try out the code from this article.
FitNesse is definitely not designed with embedding and extending in mind. But when I was willing to dive into it, I found it wasn’t that bad. I really wish my “trick” with writing my own main class was closer to the intended use, though.
[Ole Morten Amundsen] - Mar 24, 2008
I share your philosophy. I want to run everything locally, including the fitnesse server. This is my startup script
java -cp fitnesse.jar;lib\fitnesse-pom-widget-1.0-SNAPSHOT.jar;lib\maven-embedder-2.0.4-dep.jar fitnesse.FitNesse -p 8091 -r FitNesseRoot -l logs -o -e 0
Guess you’d find a way to execute the fitness server from Eclipse, but I don’t see why. It uses near to nothing of resources and don’t need to be restarted to notice updates. A simple .bat or .sh is good enough.
RELOCATION I presume you have your fixtures in source control, why not have fitnesse ialongside it? They definetly belong together.
/trunk/fitnesse /trunk/fixtures (or wherever the fixtures are located)
Then you may use the relative path “!pom ../fixtures/pom.xml”
DEBUGGER The option -html fitnesse.html in the debugger outputs the results in the same manner as pressing Test in the fitnesse page.
Sadly, there is no cigar with FitNesse. I say, use it to communicate requirements, supplement with a few test tables. It’s a wiki, terrible to maintain and opposes refactoring. I’ve committed my share of sins. Do you have any rules/guidelines for this Johannes?
Nils-Helge Garli Hegvik - Mar 27, 2008
As the author of the fitnesse plugin that Ole Morten kindly enough has mentioned, I do agree with you that it is pretty obvious that FitNesse was not built with Maven in mind, and worse, it’s practically impossible to customize! It’s not modular and extensible at all (you wouldn’t believe the troubles and hacks I had to go through to get that thing to work…)!
Anyway, you seem to have found a good solution for running FitNesse for a Maven project. I still think there’s cases where the pom widget is useful, especially when having a common FitNesse server installation and multiple projects within it.
As for me, I’ve left FitNesse and use plain Fit instead. We use Excel spreadsheets as the source and convert them to HTML tables when the tests are run. Then our build run Fit with these converted HTML tables. It’s fast, simple and more importantly, just what we need.
Andy Palmer - Apr 7, 2009
If you use
!path {java.class.path}
You get the classpath that fitnesse was called with, without the need for changing the fitnesse code.
Also, if you create a page with the following (eg. .DebugSettings)
!define COMMAND_PATTERN {java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1044 -cp %p %m}
Then, on pages you want to debug, you can just add
!include .DebugSettings
not as nice as a Debug responder, but solves the problem without writing code for FitNesse.
[Joe Hoover] - Jun 10, 2009
Just built this on OS X with Intellij. Had to remove the quotes around the classpath elements and then it worked. Also seems like Intellij attached automatically when launching RunFitnesse in debug mode. Hit a breakpoint in InheritClasspathTestResponder, this without adding DebugTestResponder. Very nice, thanks.
[krishna] - Sep 1, 2012
Four years later, how do we get this working for the latest Fitnesse library?
Johannes Brodwall - Sep 1, 2012
Hi, Krishna - I haven’t been using FitNesse much the last two years and not at all last year. (Feels great, by the way) I think that Andy’s approach is still the best and that it still works. Just add this to a FitNesse page:
!path {java.class.path}
This makes the page inherit the classpath that FitNesse was launched with.