Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Tuesday, March 10, 2009

Micro DSL meets testing

Last night I was writing a number of tests. All the tests were quite similar, so there was potential for much duplication. An example of one of the tests was this:
@Test
public void exampleTest() {
context.checking(new Expectations(){{
one(serviceA).getValue(); will(returnValue(SMALL_NUMBER));
one(serviceB).getValue(); will(returnValue(BIG_NUMBER));
}});
assertThat(new ValueChooser().chooseFrom(serviceA.getValue(),
serviceB.getValue()),
is(equalTo(SMALL_NUMBER);
}

I basically needed to assert the result of the value chooser, based on what the 2 services returned. So, to avoid duplication, instinct tells me to extract method, and parameterise variable parts. This would give you a method like so:
assertValueChosen(serviceAResponse, serviceBResponse, expectedValue);

When looking at this, it is not too difficult to tell what the parameters are, but if you actually place values into the parameter list, it can detract from the readability of the assert. For example:
assertValueChosen(SMALL_NUMBER, BIG_NUMBER, SMALL_NUMBER);

The name of the test could describe what is being asserted, and yes, this should definitely be correct because this is what is output in the JUnit results (and thus reported on), but it is quite easy (like it is with comments) to change the code, and have the name of the tests method no longer describe exactly what it is that test is testing. The name of the test could be out of date.

I had a small think about it, and decided that, what I would like would be a sentence that read like an example of behaviour. "When service A returns x, and service B returns y, then assert the value chosen is z" kind of thing.

What I decided on was to use a Micro DSL. Doing so allowed me to express my tests more clearly and not have to rely on the name of the test:
whenServiceAReturns(SMALL_NUMBER).andServiceBReturns(BIG_NUMBER).
assertValueChosenIs(SMALL_NUMBER);

Lovely! It is now impossible for this sentence to be out of date, from what is being executed.

So now, for your viewing pleasure, here is the whole test in its entirety...
public class ValueChooserTest {

private static final int SMALL_NUMBER = 5;
private static final int BIG_NUMBER = 10;

@Test
public void testAllCases() {
whenServiceAReturns(SMALL_NUMBER).andServiceBReturns(BIG_NUMBER).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(BIG_NUMBER).andServiceBReturns(SMALL_NUMBER).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(null).andServiceBReturns(SMALL_NUMBER).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(SMALL_NUMBER).andServiceBReturns(null).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(null).andServiceBReturns(null).assertValueChosenIs(0);
}

private class ValueChooserAsserter {
private Integer serviceAValue;
private Integer serviceBValue;

private final Mockery context = new JUnit4Mockery();
private final Service serviceA = context.mock(Service.class, "serviceA");
private final Service serviceB = context.mock(Service.class, "serviceB");

private ValueChooserAsserter(Integer serviceAValue) {
this.serviceAValue = serviceAValue;
}

public static ValueChooserAsserter whenServiceAReturns(Integer value) {
return new ValueChooserAsserter(value);
}

public ValueChooserAsserter andServiceBReturns(Integer serviceBValue) {
this.serviceBValue = serviceBValue;
return this;
}

public void assertValueChosenIs(Integer expectedValue) {
context.checking(new Expectations(){{
one(serviceA).getValue(); will(returnValue(serviceAValue));
one(serviceB).getValue(); will(returnValue(serviceBValue));
}});
assertThat(new ValueChooser().chooseFrom(serviceA.getValue(), serviceB.getValue()), is(equalTo(expectedValue)));
}
}

}

So people, what do you think? :)

---------- UPDATE ----------

Here is the example modified after Romilly's comments:

public class ValueChooserTest {

private static final int SMALL_NUMBER = 5;
private static final int BIG_NUMBER = 10;

@Test
public void testAllCases() {
whenServiceAReturns(SMALL_NUMBER).andServiceBReturns(BIG_NUMBER).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(BIG_NUMBER).andServiceBReturns(SMALL_NUMBER).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(null).andServiceBReturns(SMALL_NUMBER).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(SMALL_NUMBER).andServiceBReturns(null).assertValueChosenIs(SMALL_NUMBER);
whenServiceAReturns(null).andServiceBReturns(null).assertValueChosenIs(0);
}

private class ValueChooserAsserter {
private Integer serviceAValue;
private Integer serviceBValue;

private final Mockery context = new JUnit4Mockery();
private final Service serviceA = context.mock(Service.class, "serviceA");
private final Service serviceB = context.mock(Service.class, "serviceB");

private ValueChooserAsserter(Integer serviceAValue) {
this.serviceAValue = serviceAValue;
}

public static ValueChooserAsserter whenServiceAReturns(Integer value) {
return new ValueChooserAsserter(value);
}

public ValueChooserAsserter andServiceBReturns(Integer serviceBValue) {
this.serviceBValue = serviceBValue;
return this;
}

public void assertValueChosenIs(Integer expectedValue) {
context.checking(new Expectations(){{
one(serviceA).getValue(); will(returnValue(serviceAValue));
one(serviceB).getValue(); will(returnValue(serviceBValue));
}});
assertThat(new ValueChooser(serviceA, serviceB).choose(), is(equalTo(expectedValue)));
}
}

}

Monday, February 23, 2009

Micro DSL list finder versus Jedi

Toby "blog-a-lot" Weston wrote about a micro DSL style list finder that he is playing around with. His list finder looks like this:

Employee candidate = find(interviewee).in(applicants).using(superStrictCriteria);


where the list finder uses a custom comparator interface (in this case the superStrictCriteria) to determine if a target (interviewee) matches any of the list elements (applicants). I think this DSL is quite nice-but there is another library that does this for you already in a similar fashion-Jedi. Jedi is a functional programming library that allows you to express your ideas in a very computer-sciencey way. Impress your collegues and try it today!

The above example, using Jedi can be done as follows:

Employee candidate = first(select(applicants, superStrictCriteria));


where superStrictCriteria is a Filter interface which essentially boils down to:

public interface Filter <T> {
boolean execute(T value);
}


so in this case could be:

new Filter<employee>() {
public Boolean execute(Employee employee) {
return superStrictCriteria.canBeAnsweredBy(employee);
}
}

(Note: in the actual Jedi code the Filter interface extends Functor-but I left that bit out for brevity)

An advantage that you get with Jedi is that there are already a lot of functional ways to handle lists. Have a look in the jedi.functional.FunctionalPrimitives class. You can mix and match pretty much any of these functional primitives to attain quite complex list handling and criteria selection. And if this is not enough, Jedi gives you Coercions (eg. converting lists and arrays), Comparables (eg. equal, lessThan, min), FirstOrderLogic (eg. exists, all, not) and much much more!

So, although I'm not one for reinventing the wheel (I would normally use Jedi for what Toby describes), I do like to explore new styles of coding-just so long as the substance is there.