martes, noviembre 23, 2010

... Una Vez Está Bien Pero Dos Está Mal ...

Hamcrest is about making your test readable. Hamcrest is a framework that allows us to use object matchers for developing stylized sentences in assertions.

Let's look some examples:

Why write this:

double calculatedValue = 1000D;
...
assertEquals(1000, calculatedValue, 0)


When you can write this:

assertThat(calculatedValue, is(1000));

Or Why write off all this code:

List validResults = Arrays.asList("4+", "3+", "2+");
String result = .....;

assertFalse(validResults.contains(result));


When you can write:

assertThat("1+", not(isIn(validResults)));

Three points should get your attention:

I. Using assertEquals (in our case with double) is not readable, what is the meaning of third parameter in previous example ?¿ What's happen if instead of 0 I pass a 1?¿.

II. When you speak yourself repeating a requirement, you say: "calculated value should be 1000", but it is rarely common to think "1000 should be my calculated value". Moreover, for example String assertEquals method contains 2 parameters, which of two are the expected value and current value?

III. Flexible notation in contrast to JUnit notation. For specifying that an element cannot be into a collection, in Junit assertFalse is used, and that's sounds strange using with a contains method of a list you can misunderstood exactly what are being test. Look Hamcrest expression, no possible error, we are asserting that "1+" not in validResults. Hamcrest has a module for treating collections expressions, like anyOf, isOneOf, hasItem, hasItems...

But wait, there is more, you can also build you own Hamcrest Matcher. A matcher is the responsible for validating if something is what expected. Hamcrest comes with a lot of built-in Matchers, but what's happen if we need one new matcher? Let's look an example:

Hamcrest does NOT comes with File matchers. We need to validate if after our business logic, a file is created in a directory.

- In Junit like expressions, would look something like:

final File createdFile = new File("test1");
assertTrue(createdFile.exists());


- In Hamcresst with built-in matchers:

final File createdFile = new File("test1");
assertThat(createdFile.exists(), is(true));


Maybe better than JUnit but expressiveness may be missing.

- What about that:

final File createdFile = new File("test1");
assertThat(createdFile, exists());


Better I think.

But a Matcher for asserting that, does not exists, so let's write one:

public class ExistsFileMatcher extends TypeSafeMatcher {

@Override
public void describeTo(final Description description) {
description.appendText("does not exists.");
}

@Override
public boolean matchesSafely(final File expectedFile) {
return expectedFile.exists();
}

@Factory
public static Matcher exists() {
return new ExistsFileMatcher();
}

}

One will agree that is so simple, only one method that validates the expression, other one that describes the error, and finally a factory.

And this matcher can be used in conjunction with other matchers. It is also valid that one:

final File createdFile = new File("test1");
assertThat(createdFile, not(exists()));


And that's Hamcrest, don't you think is a better way to express assertions rather than plain JUnit?

2 comentarios:

Maxime Nowak dijo...

Hi Alex,

I think that you should give a look at fest-assert : http://docs.codehaus.org/display/FEST/Fluent+Assertions+Module

I've used for a few years Hamcrest and now I am only using fest-assert.
Assertions are more fluents, and in some cases more easier to use.

An example :

List newEmployees = employees.hired(TODAY);
assertThat(newEmployees).hasSize(6)
.contains(frodo, sam);

Regards,

Maxime

Alex dijo...

Thanks for the comment I will take a look.