!define TEST_SYSTEM {fit:A} 56
Uncle Bob has been busy with FitNesse lately. If you have been following him on Twitter or if you read his blog post on the subject, then you are aware of his work on Slim.
This post, however, is not about that. It is about something he did to make it possible to execute different tests in different VM’s.
By default, when you click on the test or suite buttons to run tests, FitNesse finds the tests it will run and executes them in a single VM.
If you want to select a particular test runner in FitNesse, you can add the following to a page:If you do not define this variable, then fit is the test system used to execute tests, making the first example redundant…almost.
These variable definitions are inherited. FitNesse will search up the page hierarchy to find variable definitions. If you do not define TEST_SYSTEM anywhere in a page’s hierarchy, then that test will be executed with fit. However, if any of the pages above the current page changed the runner to slim, then Slim will be the test runner.
The other thing that you can do is add a “logical-vm name” to the end of the runner. Here are two examples:On some page
On a different page
All tests under the page containing the first define run in a vm with the logical name vm1. The same is true for vm2.
By default (i.e., you have not defined TEST_SYSTEM anywhere), all tests are run in the same vm. More precisely:- When you click the test button, all tests executed as a result of that button click run in one VM.
- When you click the suite button, all tests executed as a result are executed in the same VM.
As soon as you introduce the TEST_SYSTEM variable, the tests might execute in the same VM or different VM’s.
Conceptually, there’s a default or unnamed VM under which all tests execute. As soon as a page contains a TEST_SYSTEM with the added :VMName syntax, that page and all pages hierarchically below it run in a different VM.
If for some reason you want to have two unrelated page hierarchies execute in the same VM, you can. Define the TEST_SYSTEM variable with the same logical VM name.
Why did he add this feature
I asked him to. He was working on that part of FitNesse so I figured he’d be able to add the feature for a project I’m working on.It has to do with service-level testing of a SOA-based solution. If you’re interested in hearing about that and the rationale for adding this feature to FitNesse, let me know in the comments and I’ll describe the background.
I'm glad that static typing is there to help... 13
The Background
A colleague was using FitNesse to create a general fixture for setting values in various objects rendered from a DTD. Of course you can write one per top level object, but given the number of eventual end-points, this would require a bit too much manual coding.This sounds like a candidate for reflection, correct? Yep, but rather than do that manually, using the Jakarta Commons BeanUtils makes sense – it’s a pretty handy library to be familiar with if you’re ever doing reflective programming with attributes.
package com.objectmentor.arraycopyexample; import static org.junit.Assert.assertEquals; import org.junit.Test; public class ArrayPropertySetterTest { @Test public void assertCanAssignToArrayFieldFromArrayOfObject() { Object[] arrayOfBars = createArrayOfBars(); Foo foo = new Foo(); ArrayPropertySetter.assignToArrayFieldFromObjectArray(foo, "bars", arrayOfBars); assertEquals(3, foo.getBars().length); } private Object[] createArrayOfBars() { Object[] objectArray = new Object[3]; for (int i = 0; i < objectArray.length; ++i) objectArray[i] = new Bar(); return objectArray; } }For completeness, you’ll need to see the Foo and Bar classes:
Bar
package com.objectmentor.arraycopyexample; public class Bar { }
Foo
package com.objectmentor.arraycopyexample; public class Foo { Bar[] bars; public Bar[] getBars() { return bars; } public void setBars(Bar[] bars) { this.bars = bars; } }
So an instance of a Foo holds on to an array of Bar objects; and the Foo class has the standard java-bean-esque setters and getters.
With this description of how to set an array field on a Java bean, let’s get this to actually work.
First question, how do you deal with arrays in Java? Sounds trivial, right. If you don’t mind a little pain, it’s not that bad… By dealing, I mean what happens when someone has given you an array created as follows:Object[] arrayOfObject = new Object[3]:Note that this is very different from this:
Object[] arrayOfBars = new Bar[3]:
The runtime type of these two results is different. One is array of Object; the other is Array of Bar.
This will not work:Bar[] arrayOfBar = (Bar[])arrayOfObject;This will generate a runtime cast exception. You cannot simply take something allocated as an array of objects and cast it to an array of a specific type. NO, you have to do something more like the following:
Array.newInstance(typeYouWantAnArrayOf, sizeOfArray);
That’s not too bad, right? You can then either use another method on the Array class to set the values, or you can cast the result to an appropriate array.
That’s enough information to write a generic method to copy from an array of Object to an array of a subtype of Object:public static Object[] copyToArrayOfType(Class destinationType, Object[] fromArray) { Object[] result = (Object[])Array.newInstance(destinationType, fromArray.length); for(int i = 0; i < fromArray.length; ++i) result[i] = fromArray[i]; return result; }This is a bit unruly because the caller still needs to cast the result:
Object[] arrayOfObject = new Object[] { new Foo(), new Foo(), new Foo() }; Foo[] arrayOfFoo = (Foo[])copyToArrayOfType(Foo.class, arrayOfObject);We can get rid of this cast if we use generics:
public static <T> T[] copyToArrayOfType(Class<T> destinationType, Object[] fromArray) { T[] result = (T[])Array.newInstance(destinationType, fromArray.length); for(int i = 0; i < fromArray.length; ++i) result[i] = (T) fromArray[i]; return result; }This doesn’t quite work because of type erasure, so to get this to “compile cleanly – no warnings”, you’ll need to add the following line above the method:
@SuppressWarnings("unchecked")
That’s just me telling the compiler I really think I know what I’m doing.
With this change, you can now write the following:Object[] arrayOfObject = new Object[] { new Foo(), new Foo(), new Foo() }; Foo[] arrayOfFoo = copyToArrayOfType(Foo.class, arrayOfObject);
The original problem was to take an array of Object[] and set it into a destination object’s attribute. Now we can create an array with the correct type, what next?
There are several things still remaining:- Given the name of the property, determine its underlying array type.
- Create the array (above)
- Assign the value to the underlying field
- Do some suficient hand-waving to handle exceptions
Here are solutions for each of those things:
Determine underlying type
PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor( destObject, fieldName); Class<?> destType = pd.getPropertyType().getComponentType();
Create the array
Object[] destArray = copyToArrayOfType.(destType, fromArray);
Assign the value
PropertyUtils.setSimpleProperty(destObject, fieldName, destArray);Here’s all of that put together and simply capturing all of the checked exceptions (that’s a whole other can of worms):
public static void assignToArrayFieldFromObjectArray(Object destObject, String fieldName, Object[] fromArray) { try { PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(destObject, fieldName); Class<?> destType = pd.getPropertyType().getComponentType(); Object[] destArray = copyToArrayOfType(destType, fromArray); PropertyUtils.setSimpleProperty(destObject, fieldName, destArray); } catch (Exception e) { throw new RuntimeException(e); } }
That’s all it takes to copy an array and then set the value in the field of a destination object.
Simple, right?
Sometimes static (and strong) typing can get in the way. This is one of those cases. Luckily, you can write this one and use it all over. Maybe it’s a part of the BeanUtils that I was unable to track down (probably).
Using Band XI's FitNesse Plugin for Eclipse 24
Our friends at Band XI have an Eclipse plugin for FitNesse. I spent a few hours trying it out for a client. Here are some notes to help you use it.
Band XI’s plugin makes it easy to run FitNesse from within Eclipse and to edit the Java code and FitNesse pages in one place. Hence, no more switching between windows while you’re making your acceptance tests pass.
The download and installation page describes how to get started. While it only lists support for Eclipse 3.1 and 3.2, it seems to work fine in Eclipse 3.3 and 3.4 M7.
After you have installed the plugin, you will have two new toolbar buttons, one to start and stop a local FitNesse server and one to start and stop a remote server.
You configure where these servers are located in the Preferences > FitNesse page. These settings apply for the whole workspace. It might be nice to be able to configure them on a project by project basis, but that’s not supported (and maybe not that important, either).
You also get a new project option in the “New” wizard. Java > FitNesse > FitNesse Example Project creates a new Java project with src, FitNesseRoot and fixtures source directories. The include all the standard example acceptance tests and code that comes with the FitNesse download.
I encountered a little oddity the first time I started the local server by clicking the toolbar button. The usual front page, with links for the examples, manual, etc. weren’t shown on the FitNesse front page.
I determined that this happens because the default FitNesseRoot in the preferences is ”.”. Instead, start the server the first time by right-clicking on the FitNesseRoot folder, then select the FitNesse menu and the Launch FitNesse runtime on this root folder option. From then on, it will launch with this folder as the default, until you select a different one.
Using your own FitNesse installation and projects
Here’s what I did to work with existing projects and use a separate FitNesse installation outside of an Eclipse project.
If you have another project with the FitNesseRoot folder, you can use it as just described (i.e., using the context menu).
Otherwise, go to the Preferences > FitNesse page and paste the full path of your alternative FitNesseRoot in the “local root directory” field. Be sure to delete the ”.” value that is present by default and also watch for spaces before and after the string. It appears that they aren’t removed, causing mysterious behavior…
To build fixtures in your Java project, add the FIT_LIB and FITNESSE_LIB “variables” (defined by the plugin) to the Java build path for your project (using the project properties dialog). The default values of these environment variables should be fine unless you are running a different version of FIT or FitNesse in your separate installation (in which case you can change the values as needed).
Finally, set your class path in your FitNesse pages to point to the correct directory for the compiled classes. For example,
!path /home/me/projects/workspace/MyFitProject/bin
That should do it!