Lors de l’écriture de tests unitaires, on peut se retrouver à écrire ce genre de code :

assertEquals(42, answer);
assertFalse(answer == 34);
assertTrue(info.matches("color") || info.matches("colour"));
assertTrue(colorsList.contains("red"));
assertTrue(s instanceof MagicString);

Mais JUnit propose depuis la version 4.4 une nouvelle syntaxe à base de Matchers. En fait, cette syntaxe n’est pas totalement nouvelle : elle est empruntée à JMock qui possède déjà les Matchers JMock pour décrire les contraintes d’un mock. L’idée vient de Joe Walnes dans son article Flexible JUnit assertions with assertThat().

Les assertions précédentes peuvent alors s’écrire de la manière suivante :

assertThat(answer, is(42));
assertThat(answer, is(not(34)));
assertThat(info, either(containsString("color")).or(containsString("colour")));
assertThat(colorsList, hasItem("red"));
assertThat(s, instanceOf(MagicString.class));

Les avantages sont multiples :

  • Ça se lit mieux : le développeur communique donc mieux son intention aux autres développeurs, ce qui est essentiel !
  • Contrairement aux méthodes assertEquals et consorts, on ne risque pas de tromper dans l’ordre des arguments : la syntaxe d’assertEquals est assertEquals([expected], [actual]) mais beaucoup de gens se trompent et écrivent assertEquals(answer, 42). Avec assertThat, aucun risque.
  • Les matchers peuvent être modifiés: par exemple : not(s) pour inverser la condition, either(s).or(t) pour combiner des conditions, each(s) pour appliquer une condition sur chaque élément d’une collection, afterFiveSeconds(s) pour attendre 5 secondes avant de tester…
  • En cas d’échec de l’assertion, les messages sont beaucoup plus lisibles. Si on reprend l’exemple précédents, avec les assertXxx classiques, les messages ressembleraient à ça :
    assertEquals(42, answer);
     => expected:<42> but was:<36>
    assertFalse(answer == 34);
     => junit.framework.AssertionFailedError:
    assertTrue(info.matches("color") || info.matches("colour"));
     => junit.framework.AssertionFailedError:
    assertTrue(colorsList.contains("red"));
     => junit.framework.AssertionFailedError:
    assertTrue(s instanceof MagicString);
     => junit.framework.AssertionFailedError:
    

    Avec assertThat, les message ressemblent plutôt à ça :

    assertThat(answer, is(42));
     => Expected: is <42>
             got: <36>
    assertThat(answer, is(not(34)));
     => Expected: is not <34>
             got: <34>
    assertThat(info, either(containsString("color")).or(containsString("colour")));
     => Expected: (a string containing "colour" or a string containing "color")
             got: "rainbow"
    assertThat(colorsList, hasItem("red"));
     => Expected: a collection containing "hello"
             got: <[bonjour, ia orana]>
    assertThat(s, instanceOf(MagicString.class));
     => Expected: an instance of sandbox.FooTest$MagicString
             got: "polop"
    

    Inutile de dire qu’il est beaucoup plus facile de débugguer des tests écrits avec assertThat…

  • Il est plus facile d’écrire des Matcher maison plutôt que des méthodes assertXxxx maison. En plus ces Matchers pourront être réutilisés dans des tests JMock.
  • C’est sexy :)

Pour plus d’informations, je vous recommande la lecture de ce billet. Il est en français, il est très détaillé et je suis tombé dessus en écrivant cet article.

boîte de conserveEn java, n’importe quel objet implémentant l’interface Serializable peut être converti facilement en une séquence d’octets. Cette même séquence peut ensuite être reconverti en l’objet original. Ce mécanisme est par exemple utilisé pour transmettre des objets par RMI.

Voici une petite méthode pour vérifier que votre classe est bien sérialisable

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
 
public class Assert {
    /**
     * Assert that an object is serializable by dumping it and then reading it
     * back and finally by checking the original object and the dumped and read
     * object are equals.
     *
     * <p>Obviously, the method <code>equals()</code> has to be implemented
     * correctly for this method can work.
     * @param objOriginal the instance which will be checked against serialization;
     * must implement <code>equals()</code> or it won't work
     */
    public static void assertSerializable(Object objOriginal) {
        try {
            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
            final ObjectOutputStream out = new ObjectOutputStream(stream);
            out.writeObject(objOriginal);
            out.close();
 
            final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(stream.toByteArray()));
            final Object objSerialized = in.readObject();
            assertEquals(objOriginal, objSerialized);
        } catch (IOException e) {
            fail("unable to serialize or deserialize object " + objOriginal + ": " + e.getMessage());
        } catch (ClassNotFoundException e) {
            // unlikely to happen...
            fail(e.getMessage());
        }
    }
}
</p>

Lire la suite…