31
JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Embed Size (px)

Citation preview

Page 1: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

JUnitTest Infected: Programmers Love Writing Tests

A little test, a little code, a little test, a little code…

Page 2: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Enoncé du problèmePortefeuille de devises

Opérations arithmétiques avec diverses devises

On ne peut pas simplement convertir une devise dans une autre

Il n’existe pas un seul taux de conversionOn peut vouloir comparer la valeur d’un portefeuille au taux d’hier avec celui au taux d’aujourd’hui

Page 3: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Public class Money {

private int fAmount;    

//ISO three letter abbreviation (USD, CHF, etc.). private String fCurrency; 

  

public Money(int amount, String currency){        fAmount= amount;         fCurrency= currency;   

  }     public int amount() {

return fAmount;     }    

public String currency() {         return fCurrency;     }

public Money add(Money m) {      return new Money(amount()+m.amount(),currency()); }

}

Page 4: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Addition d’instance de Money possédant la même devise, le montant de l’instance résultante de Money est la somme des deux montants

public class MoneyTest extends TestCase {     //…     public void testSimpleAdd() {         Money m12CHF= new Money(12, "CHF");  // (1)        Money m14CHF= new Money(14, "CHF");                 Money expected= new Money(26, "CHF");         Money result= m12CHF.add(m14CHF);    // (2)  

assertTrue(expected.equals(result)); // (3)     }}

1. Code qui crée les instance qui vont interagir pour le test. Le contexte du test est généralement appelé fixture.

2. Code qui active les instance de la fixture. 3. Code qui vérifie le résultat.

Page 5: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Deux instance de Money sont considérées égales si elles ont les mêmes montants et devises

public void testEquals() {

  Money m12CHF= new Money(12, "CHF"); Money m14CHF= new Money(14, "CHF");  

assertTrue(!m12CHF.equals(null));   

assertEquals(m12CHF, m12CHF);

   assertEquals(m12CHF, new Money(12, "CHF")); // (1)  

   assertTrue(!m12CHF.equals(m14CHF));}

assertTrueDéclenche un échec qui est loggé par JUnit lorsque l’argument n’est pas vrai.

assertEquals Test pour l’égalité à l’aide de la méthode « equals «  Log les valeurs « textuelles » (printString) de chaque instance si elles diffèrent.

Page 6: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

La méthode equals de la classe Money

public boolean equals(Object anObject)

{  

if (anObject instanceof Money){

    Money aMoney= (Money)anObject;

     return aMoney.currency().equals(currency())  

           && amount() == aMoney.amount();

    }

   return false;

}

Page 7: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Common fixtureDuplication de code pour mettre en place les tests. La méthode setUp

Pour réutiliser le code commun de mise en place des tests. • Placer les objets de la fixture dans des variables d’instance de la sous-classe de TestCase

initialize them by overridding the setUp method.

La méthode tearDownOpération symétrique de la méthode setUpLa redéfinir pour nettoyer la fixture à la fin d’un test.

Chaque test s’exécute dans sa propre fixture JUnit invoque setUp et tearDown pour chaque test Pour ne pas avoir d’effet de bord entre les tests

Page 8: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Réécriture des tests – supprimer la duplication de code

public class MoneyTest extends TestCase {

   private Money f12CHF;    private Money f14CHF;

    protected void setUp() {         f12CHF= new Money(12, "CHF");        f14CHF= new Money(14, "CHF");      } public void testEquals() {

assertTrue(!f12CHF.equals(null));     assertEquals(f12CHF, f12CHF);     assertEquals(f12CHF, new Money(12, "CHF"));     assertTrue(!f12CHF.equals(f14CHF)); }

public void testSimpleAdd() {     Money expected= new Money(26, "CHF");     Money result= f12CHF.add(f14CHF);      assertTrue(expected.equals(result)); }

    protected void tearDown() {         f12CHF= null;        f14CHF= null;      }}

Page 9: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Exécution d’un testStatique

Redéfinir la méthode runTest héritée de TestCase Invoquer le test unitaire désiré

TestCase test= new MoneyTest("simple add"){     public void runTest() {

        testSimpleAdd();     } };

DynamiqueUtiliser la réflexivité définie dans la méthode runTest

TestCase test= new MoneyTest("testSimpleAdd");

Page 10: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Création d’une instance de tests

Anonymous inner class

--------------------------public void runTest( ){

testSimpleAdd();

}

fName = «Simple add » 

TestCase

---------------------public void runTest( ){

……

}

runTest()

MoneyTest

------------------------

Page 11: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Création d’une instance de tests

fName = «testSimpleAdd » 

TestCase

-----------------------------------------public void runTest( ){

trouver la méthode de sélecteur fName

exécuter cette méthode

}

runTest()

MoneyTest

------------------------

Page 12: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Test suiteTestSuite est un patron de conception Composite (Design Pattern)

Un TestSuite peut exécuter une collection de tests. TestSuite et TestCase implémentent l’ interface Test qui définit les méthodes permettant d’éxécuter un test. Permet la création de suite de tests en composant arbitrairement les TestCases et les TestSuites.

public static Test suite() {     TestSuite suite= new TestSuite();    suite.addTest(new MoneyTest("testEquals"));    suite.addTest(new MoneyTest("testSimpleAdd"));    return suite; }

Page 13: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Static test suitepublic static Test suite() {    TestSuite suite= new TestSuite();     suite.addTest(new MoneyTest("money equals") {

            protected void runTest() {

testEquals(); }         }     );

suite.addTest( new MoneyTest("simple add") {              protected void runTest() {

testSimpleAdd(); }         }     );return suite;

}

Page 14: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Création dynamique de suites de tests

Transmettre simplement la classe à tester à la classe TestSuite TestSuite extrait les méthodes de tests automatiquement

public static Test suite() {

return new TestSuite(MoneyTest.class);

}

Page 15: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…
Page 16: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…
Page 17: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…
Page 18: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…
Page 19: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Opérations arithmétiques sur des devises différentesIl n’existe pas un taux de change uniquePour contourner ce problème, introduction de la classe MoneyBag qui diffère les conversions en fonction des taux de change.

class MoneyBag {     private Vector fMonies= new Vector();

    MoneyBag(Money m1, Money m2) {         appendMoney(m1);         appendMoney(m2);    

}     MoneyBag(Money bag[]) {

        for (int i= 0; i < bag.length; i++)             appendMoney(bag[i]);  

   }}

appendMoney Ajoute une instance de Money à la liste des Moneys S’occupe de consolider les Moneys possédant la même devise.

Page 20: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Tester MoneyBag

protected void setUp() {     f12CHF= new Money(12, "CHF");     f14CHF= new Money(14, "CHF");     f7USD=  new Money( 7, "USD");     f21USD= new Money(21, "USD");     fMB1= new MoneyBag(f12CHF, f7USD);     fMB2= new MoneyBag(f14CHF, f21USD); }

public void testBagEquals() {     assertTrue(!fMB1.equals(null));     assertEquals(fMB1, fMB1);     assertTrue(!fMB1.equals(f12CHF));    assertTrue(!f12CHF.equals(fMB1));    assertTrue(!fMB1.equals(fMB2)); }

Page 21: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

public Money add(Money m) {     if (m.currency().equals(currency()) )         return new Money( amount()+m.amount(),

currency());     return new MoneyBag(this, m); }

Il existe maintenant deux représentations pour les Moneys : Money et MoneyBag

Les cacher au niveau du code client. Introduire une interface IMoney implémentée par les deux représentations.

interface IMoney {     public abstract IMoney add(IMoney aMoney);    

//… }

Page 22: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Tests de l’addition de IMoneypublic void testMixedSimpleAdd() { 

    // [12 CHF] + [7 USD] == {[12 CHF][7 USD]} 

    Money bag[]= { f12CHF, f7USD }; 

    MoneyBag expected= new MoneyBag(bag);   

   assertEquals(expected, f12CHF.add(f7USD)); 

}

Les autres tests suivent le même patron:

testBagSimpleAdd – addition d’un MoneyBag à un Money simple

testSimpleBagAdd - addition d’un Money simple à un MoneyBag

testBagBagAdd – addition de deux MoneyBags

Page 23: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Suite de tests pour MoneyBagpublic static Test suite() {

    TestSuite suite= new TestSuite();

    suite.addTest(new MoneyTest("testMoneyEquals"));

    suite.addTest(new MoneyTest("testBagEquals"));

    suite.addTest(new MoneyTest("testSimpleAdd"));

    suite.addTest(new MoneyTest("testMixedSimpleAdd"));

    suite.addTest(new MoneyTest("testBagSimpleAdd"));  

   suite.addTest(new MoneyTest("testSimpleBagAdd"));

    suite.addTest(new MoneyTest("testBagBagAdd"));

   return suite;

}

Page 24: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Implémentation de l’additionLe défi de cette implémentation est de gérer les diverses combinaisons de Money et MoneyBag.

Le patron de conception « Double dispatch » solution élégante à ce problème (Visitor DP). L’idée derrière le double dispatch est d’utiliser un appel supplémentaire pour découvrir le type des arguments impliqués. Transmettre au paramètre un message dont le (nouveau) nom est

• le nom de la méthode originale • suivi du nom de la classe du destinataire original.

class Money implements IMoney {     public IMoney add(IMoney m) {         return m.addMoney(this);    

}     //… }class MoneyBag implements IMoney {     public IMoney add(IMoney m) {         return m.addMoneyBag(this);     }    

//… }

Page 25: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Implémentation du double dispatch dans Money

class Money implements IMoney {

  public IMoney add(IMoney m) {       return m.addMoney(this);     }

public IMoney addMoney(Money m) {  if (m.currency().equals(currency()) )     

return new Money( amount()+m.amount(), currency());  

return new MoneyBag(this, m); }

public IMoney addMoneyBag(MoneyBag s) {     return s.addMoney(this); }}

Page 26: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Implémentation du double dispatch dans MoneyBag

class MoneyBag implements IMoney {

    public IMoney add(IMoney m) {

        return m.addMoneyBag(this);

    }   

 

public IMoney addMoney(Money m) {   

  return new MoneyBag(m, this); }

public IMoney addMoneyBag(MoneyBag s) {  

   return new MoneyBag(s, this);

}

}

Page 27: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Test de la simplification de MoneyBag

public void testSimplify() {   

  // {[12 CHF][7 USD]} + [-12 CHF] == [7 USD]    

Money expected= new Money(7, "USD");

   assertEquals( expected,

fMB1.add(new Money(-12, "CHF")));

}

// … Test fails

Page 28: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Implémentation de la simplification dans MoneyBag

class MoneyBag implements IMoney {     public IMoney add(IMoney m) {         return m.addMoneyBag(this);     }   

public IMoney addMoney(Money m) {      return new MoneyBag(m, this).simplify(); }

public IMoney addMoneyBag(MoneyBag s) {      return new MoneyBag(s, this).simplify(); }

private IMoney simplify() {     if (fMonies.size() == 1)          return (IMoney)fMonies.firstElement();     return this; } }

Page 29: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

ConclusionUn peu de tests, un peu de code, un peu de tests, un peu de code…

Capturer l’intention dans les tests

Le code des tests est comme le code du modèleIl fonctionne mieux s’il est bien factorisé.

Garder les vieux tests fonctionnels est aussi important que d’écrire de nouveaux tests qui fonctionnent.

Lorsque l’on a envie d’écrire un énoncé « print » ou une instruction de déverminage, écrire un test à la place

JUnit : une manière de tester qui demande un faible investissementPlus rapide, plus productif, plus prévisible et moins de stress.

Il devient possible de refactoriser plus agressivement une fois que les tests sont disponibles.

Page 30: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Interface commune pour les classes Money et MoneyBag

interface IMoney {public abstract IMoney add(IMoney m);

/** * implementing double dispatch */IMoney addMoney(Money m);IMoney addMoneyBag(MoneyBag s);

public abstract boolean isNull();public abstract IMoney multiply(int factor);public abstract IMoney negate();public abstract IMoney subtract(IMoney m);

}

Page 31: JUnitTest Infected: Programmers Love Writing Tests A little test, a little code, a little test, a little code…

Références

Erich Gamma and Kent Beck, JUnitTest Infected: Programmers Love Writing Tests, Java Report, July 1998, Volume 3, Number 7

http://junit.sourceforge.net/doc/testinfected/testing.htm