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: