Automating Fit Tests in Fitnesse Server from Cruise Control, Hudson, Bamboo via Ant

Here’s an example of calling Fit tests defined in Fitnesse from Ant. This can be called from a continuous integration server like cruise control, hudson, bamboo, luntbuild, pmeasy:

<target name="fitnesse-tests"
           description="fitnesse-tests target can be called from CC"
           depends="custom-init, compile-fit"
>
 
 <java classname="fitnesse.runner.TestRunner"
           fork="true" resultproperty="FitRegressionResult">
    <arg line="-results fit-tests/reports/fitnesse-results.html
          10.99.99.99 80 FitnessePageName"/>
    <classpath><path refid="custom.classpath" /></classpath>
 </java>
<!-- xml file processed by cruise control -->
 <java classname="fitnesse.runner.FormattingOption" fork="true">
  <arg line="fit-tests/reports/fitnesse-results.html xml
             ${fit.report.dir}/fit-results.xml
             10.99.99.99 80 FitnessePageName"/>
  <classpath><path refid="custom.classpath" /></classpath>
 </java>
 
 <!-- fail if any fit test errors -->
 <condition property="fit-failures">
   <equals arg1="${FitRegressionResult}" arg2="0"/>
 </condition>
 <fail message="${FitRegressionResult} Fitnesse test(s) failed."
        unless="fit-failures" />
</target>

Fitnesse is an excellent communication and collaboration tool between developers, QA and clients. It works well in agile development environments where emphasis and priority is put on automating tests.

If you have any questions, feel free to contact Todd McGrath at http://www.supergloo.com/contact.htm

Example of Java Acceptance Testing with Fit

Fit testing provides a way for non-developers to participate in test driven development. Fit testing facilitates collaboration and communication in applications that need to verify results from calculations. From the Fit testing website, http://fit.c2.com/:

Great software requires collaboration and communication. Fit is a tool for enhancing collaboration in software development. It’s an invaluable way to collaborate on complicated problems–and get them right–early in development.

Let’s run an example of using Fit tests in your Java application. This example assumes you are using Ant to build.

First, pick up the AntFit ant task from http://www.cmdev.com/antfit/. This will provide the code to call Fit tests from your Ant build file.

<taskdef name="antfit" classname="com.cmdev.fit.ant.FitTask" classpathref="antfit.classpath">
</taskdef>

and calling it:

<target name="test-fit" depends="init,compile" description="Executes the Fit tests">
<antcall target="populateDb"></antcall></target>
 
<!-- This uses a whole different set of folders -->
 
<property name="our.fit.dir" value="${basedir}/../fit"></property>
<property name="our.fit.src.dir" value="${our.fit.dir}/src"></property>
<property name="our.fit.test.dir" value="${our.fit.dir}/tests"></property>
<property name="our.fit.report.dir" value="${our.report.dir}/fit"></property>   <mkdir dir="${our.fit.report.dir}"></mkdir>  <!-- Now compile the fit tests -->
<property name="our.fit.compiled.dir" value="${our.work.dir}/compiled-fit"></property>   <mkdir dir="${our.fit.compiled.dir}">
<javac srcdir="${our.fit.src.dir}" destdir="${our.fit.compiled.dir}" target="1.4" fork="true" memoryMaximumSize="512m">
<classpath></classpath></javac></mkdir>
<path refid="our.classpath.buildtime"></path>
<path location="${our.test.compiled.dir}"></path> <!-- Now execute the fit tests -->
<antfit destdir="${our.fit.report.dir}" usewiki="false" fork="true">
<classpath></classpath></antfit>
<path location="${our.fit.compiled.dir}"></path>
<path refid="our.classpath.buildtime"></path>
<path location="${our.test.compiled.dir}"></path> <fileset dir="${our.fit.test.dir}">
<include name="**/*.html"></include>
</fileset>

In this example, we are not going to use a wiki (useWiki=”false”) for creating tests. We can cover that in a later example.Next, let’s look at some Java source code for Fit tests:

import java.text.NumberFormat;
import org.apache.commons.lang.StringUtils;
import util.RateFetcher;
import com.feecalc.vo.choice.BlendedResult;
import fit.ColumnFixture; 
/**
* Automates testing the standard and discount fee for the standard tiers. Does all of its
* calculations based on Equity, assuming that if the code works for Equity, it will work for any of
* them.
*
* @author robert.c.fischer
*/
public class FeeFixture extends ColumnFixture {

public double customRate;
protected static final String ZERO = “0″;
public double value;
public double discount;
public String assetType;

/**
* @return The standard fee.
*/
public String standardFee() {
return moneyToString(generateBlendedResult().getStandardFeeAnnual());
}

/**
* @return The discount fee.
*/
public String discountFee() {
return moneyToString(generateBlendedResult().getDiscountedFeeAnnual());
}

/**
* @return The discount blended rate
*/
public String discountRate() {
return percentToString(generateBlendedResult().getDiscountBlendedRate());
}

/**
* @return The standard blended rate
*/
public String standardRate() {
return percentToString(generateBlendedResult().getStandardBlendedRate());
}

/**
* @return The standard internal fee (post-”haircut” amount)
*/
public String internalStandardFee() {
return moneyToString(generateBlendedResult().getStandardRepFeeAnnual());
}

/**
* @return The discounted internal fee (post-”haircut” amount)
*/
public String internalDiscountFee() {
return moneyToString(generateBlendedResult().getDiscountedRepFeeAnnual());
}

/**
* @return The standard fee.
*/
public String getProposedClientDiscount() {
return percentToString(generateBlendedResult().getProposedClientDiscount());
}

protected static final String moneyToString(final double value) {
final NumberFormat format = NumberFormat.getInstance();
format.setMinimumFractionDigits(2);
format.setMaximumFractionDigits(2);
format.setGroupingUsed(false);
return format.format(value);
}

protected static final String percentToString(final double value) {
final NumberFormat format = NumberFormat.getInstance();
format.setMinimumFractionDigits(2);
format.setMaximumFractionDigits(2);
format.setGroupingUsed(false);
return format.format(value);
}

protected BlendedResult generateBlendedResult() {

final BlendedResult result = new BlendedResult();

// Set the rates
setEquityRates(result);
setCashRates(result);
setMutualRates(result);

// Set the test values provided by the user
if (StringUtils.isBlank(assetType) || assetType.toLowerCase().startsWith(”eq”)) {
result.setEquityDiscount(discount);
result.setEquityAmount(value);
result.setEquityRateTier(5, customRate);
} else {
result.setEquityDiscount(0);
result.setEquityAmount(0);
}
if (StringUtils.isNotBlank(assetType) && assetType.toLowerCase().startsWith(”mu”)) {
result.setMutualAmount(value);
result.setMutualDiscount(discount);
result.setMutualRateTier(5, customRate);
} else {
result.setMutualAmount(0);
result.setMutualDiscount(0);
}
if (StringUtils.isNotBlank(assetType) && assetType.toLowerCase().equals(”ca”)) {
result.setCashAmount(value);
result.setCashDiscount(discount);
result.setCashRateTier(5, customRate);
} else {
result.setCashAmount(0);
result.setCashDiscount(0);
}

return result;
}

private static final void setEquityRates(final BlendedResult result) {
final double[] equityRates = new RateFetcher().getEquityRates();
if (equityRates.length < 4) {
throw new RuntimeException(”Only have ” + equityRates.length
+ ” equity rates — need 4″);
}
for (int i = 0; i < equityRates.length; i++) {
result.setEquityRateTier(i + 1, equityRates[i]);
}
}

private static final void setCashRates(final BlendedResult result) {
final double[] cashRates = new RateFetcher().getCashRates();
if (cashRates.length < 4) {
throw new RuntimeException(”Only have ” + cashRates.length + ” cash rates — need 4″);
}
for (int i = 0; i < cashRates.length; i++) {
result.setCashRateTier(i + 1, cashRates[i]);
}
}

private static final void setMutualRates(final BlendedResult result) {
final double[] mutualRates = new RateFetcher().getMutualRates();
if (mutualRates.length < 4) {
throw new RuntimeException(”Only have ” + mutualRates.length
+ ” mutual rates — need 4″);
}
for (int i = 0; i < mutualRates.length; i++) {
result.setMutualRateTier(i + 1, mutualRates[i]);
}
}
}
Here’s the source that’s used in the Fit tests:

feecalc.FlatRateFeeFixture  
cash equity mutual flatRate assetSum() flatRateFee() comparisonFee()
300000 100000 100000 1 500000 5000.0 4975.0
4000000 4000000 4000000 0.25 12000000 30000.00 93875.00
100000 10000 10000 0.50 120000 600.00 850.00
5000 5000 5000 0.75 15000 112.50 200.00
130000 100000 10000 1.00 240000 2400.00 2800.00

And here is an example of the results after the Java Fit tests are run:

feecalc.FlatRateFeeFixture  
cash equity mutual flatRate assetSum() flatRateFee() comparisonFee()
300000 100000 100000 1 500000 5000.0 4975.0
4000000 4000000 4000000 0.25 12000000 30000.00 93875.00
100000 10000 10000 0.50 120000 600.00 850.00
5000 5000 5000 0.75 15000 112.50 200.00
130000 100000 10000 1.00 240000 2400.00 2800.00

Yae, all green. If there was an incorrect result, the errant result table cell would be red instead of green.

Hat tip to my co-worker Robert Fischer for writing the Java code and Fit test HTML.

More information on Fit testing can be found at:
http://fit.c2.com/

And for those that want a physical book:

ServiceCycle is a registered trademark of Supergloo, inc..