Tests unitaires mock_kesako_20130516

Preview:

DESCRIPTION

Vous subissez les régressions à chaque livraison ? Vous ne voyez pas l’intérêt des tests unitaires car ils ne servent qu’à tester des additions ? Les tests ne sont pas applicables à votre projet car il est trop complexe ? Si c’est le cas, suivez David dans la quête du Test Driven Development. Vous rencontrerez pléthore d’ennemies contre lesquels vous aurez à combattre : bugs, complexité, code statique, couplage fort. Ils essaieront de vous barrer la route, mais heureusement, vous pourrez compter sur vos alliés jUnit, Mockito, refactoring et injection/dépendance. David finira la soirée par une démonstration pratique sur un exercice de refactoring.

Citation preview

Bienvenue !

❤=+

réseaux sociaux

Prochaines conférences : http://soat.fr

twitter : @soatgroup / @soatexpertsjava

slideshares : http://fr.slideshare.net/soatexpert

Aller plus loin : http://blog.soat.fr

Test unitaire ? Mock ? TDD ? Kezako ?en finir avec les régressions par David Wursteisen

16 MAI 2013

Nouveau client !

Nouveau projet !

La documentation explique le code /** * * @param xml : document xml représentant le swap * @return objet Swap */ public Swap parse(String xml) { Swap swap = new Swap(); String currency = getNodeValue("/swap/currency", xml); swap.setCurrency(currency); /* beaucoup de code... */ Date d = new Date(); if(test == 1) { if(d.after(spotDate)) { swap.setType("IRS"); } else { swap.setType("CURVE"); } } else if (test == 2) { if(d.after(spotDate)) { swap.setType("IRS"); } else { swap.setType("CURVE"); } } /* encore beaucoup de code... */ return swap; }

La documentation explique le code 1 /** 2 * 3 * @param xml : document xml représentant le swap 4 * @return objet Swap 5 */ 6 public Swap parse(String xml) { 7 Swap swap = new Swap(); 8 String currency = getNodeValue("/swap/currency", xml); 9 swap.setCurrency(currency);[...] /* beaucoup de code... */ 530 Date d = new Date(); 531 if(test == 1) { 532 if(d.after(spotDate)) { 533 swap.setType("IRS"); 534 } else { 535 swap.setType("CURVE"); 536 } 537 } else if (test == 2) { 538 if(d.after(spotDate)) { 539 swap.setType("IRS"); 540 } else { 541 swap.setType("CURVE"); 542 } 543 }[...] /* encore beaucoup de code... */1135 return swap;1136 }

1000 LIGNES !

La documentation a raison

1 /**2 * Always returns true.3 */4 public boolean isAvailable() {5 return false;6 }

La documentation a raison

1 /**2 * Always returns true.3 */4 public boolean isAvailable() {5 return false;6 }

WTF ?!?

La documentation n’est plusfiableune aideutile

4 obj = networkService.getObject("product", id);

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

Faire du code qui marche

4 obj = networkService.getObject("product", id);

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

Faire du code qui marche

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

2 Object obj = null; 5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }

Faire du code qui marche

NullPointerException !

4 account = (BankAccount) service.getProduct(this.name);5 account.deposit();

1 public void affiche(BankAccount account, ProductService service) {2 System.out.println("Bank Account : "+this.name);3 System.out.println("Autres informations : ");4 account = (BankAccount) service.getProduct(this.name);5 account.deposit();6 System.out.println(account);7 System.out.println("=== FIN ===");8 9 }

Code simple

HTTP://EN.WIKIPEDIA.ORG/WIKI/FILE:JEU_DE_MIKADO.JPG

4 account = (BankAccount) service.getProduct(this.name);5 account.deposit();

1 public void affiche(BankAccount account, ProductService service) {2 System.out.println("Bank Account : "+this.name);3 System.out.println("Autres informations : ");4 account = (BankAccount) service.getProduct(this.name);5 account.deposit();6 System.out.println(account);7 System.out.println("=== FIN ===");8 9 }

Code simple

HTTP://EN.WIKIPEDIA.ORG/WIKI/FILE:JEU_DE_MIKADO.JPG

4 account = (BankAccount) service.getProduct(this.name);5 account.deposit();

1 public void affiche(BankAccount account, ProductService service) {2 System.out.println("Bank Account : "+this.name);3 System.out.println("Autres informations : ");4 account = (BankAccount) service.getProduct(this.name);5 account.deposit();6 System.out.println(account);7 System.out.println("=== FIN ===");8 9 }

Code simple

HTTP://EN.WIKIPEDIA.ORG/WIKI/FILE:JEU_DE_MIKADO.JPG

C’est compliqué de fairesimpleun testune évolution

Bonne nouvelle !

Je vous prend dans mon équipe !

HTTP://WWW.SXC.HU/PROFILE/HOTBLACK

COMMENT VA T’ON POUVOIR S’EN SORTIR ?

PRINCIPE DE BASE

Ecriture du test

Lancementdu test

Ecriture du test

Lancementdu test

Correction de l’implémentation

Ecriture du test

KO

Lancementdu test

Correction de l’implémentation

Ecriture du test

Fin

KO

OK

Lancementdu test

Correction de l’implémentation

Ecriture du test

Fin

KO

OK

REFACTOR

Toujours faire le test cassant avant le test passant(pour tester le test)

TEST UNITAIREtester la plus petite unité de code possible

TEST UNITAIREtester la plus petite unité de code possible

SIMPLE

TEST UNITAIREtester la plus petite unité de code possible

SIMPLEFEEDBACK

TEST UNITAIREtester la plus petite unité de code possible

SIMPLEFEEDBACKCOÛT

$400 MILLIONS

EXEMPLE

1 @Test2 public void can_deposit() {3 bankAccount.setBalance(50);4 5 boolean result = bankAccount.deposit(1000);6 7 assertTrue(result);8 assertEquals(1050, bankAccount.getBalance());9 }

3 bankAccount.setBalance(50);

Test d’abord !

1 @Test2 public void can_deposit() {3 bankAccount.setBalance(50);4 5 boolean result = bankAccount.deposit(1000);6 7 assertTrue(result);8 assertEquals(1050, bankAccount.getBalance());9 }

3 bankAccount.setBalance(50);

Test d’abord !

5 boolean result = bankAccount.deposit(1000);

1 @Test2 public void can_deposit() {3 bankAccount.setBalance(50);4 5 boolean result = bankAccount.deposit(1000);6 7 assertTrue(result);8 assertEquals(1050, bankAccount.getBalance());9 }

Test d’abord !

1 @Test2 public void can_deposit() {3 bankAccount.setBalance(50);4 5 boolean result = bankAccount.deposit(1000);6 7 assertTrue(result);8 assertEquals(1050, bankAccount.getBalance());9 }

Test d’abord !

7 assertTrue(result);8 assertEquals(1050, bankAccount.getBalance());

Test d’abord !

1 boolean deposit(int amount) {2 return false;3 }

Test d’abord !

1 boolean deposit(int amount) {2 return false;3 }

Test d’abord !

1 boolean deposit(int amount) {2 bal = bal + amount;3 return true;4 }

Test d’abord !

1 boolean deposit(int amount) {2 bal = bal + amount;3 return true;4 }

Test d’abord !

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

Test d’abord !

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

3 bankAccount.setBalance(100);

Test d’abord !

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

5 boolean result = bankAccount.deposit(-20);

Test d’abord !

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());

Test d’abord !

1 boolean deposit(int amount) {2 bal = bal + amount;3 return true;4 }

Test d’abord !

1 boolean deposit(int amount) {2 bal = bal + amount;3 return true;4 }

Test d’abord !

1 boolean deposit(int amount) {2 if(amount < 0) {3 return false;4 }5 bal = bal + amount;6 return true;7 }

Test d’abord !

1 boolean deposit(int amount) {2 if(amount < 0) {3 return false;4 }5 bal = bal + amount;6 return true;7 }

Acteur / Action / Assertion

les «3 A» : Acteur/Action/Assertion

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

les «3 A» : Acteur/Action/Assertion

3 bankAccount.setBalance(100);

Acteur

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

les «3 A» : Acteur/Action/Assertion

5 boolean result = bankAccount.deposit(-20);

Action

1 @Test2 public void cant_deposit_with_negative_amount() {3 bankAccount.setBalance(100);4 5 boolean result = bankAccount.deposit(-20);6 7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());9 }

les «3 A» : Acteur/Action/Assertion

7 assertFalse(result);8 assertEquals(100, bankAccount.getBalance());

Assertions

KISS

KISSKEEP IT SIMPLE (AND STUPID)

KISSKEEP IT SIMPLE (AND STUPID)

7 CustomerDTO targetDTO = this.serviceImpl.getCustomer("ABC99"); 8 9 // Vérifier.10 Assert.assertNotNull("Extra non trouvé.", targetDTO);11 Assert.assertEquals("Les accountId doivent être identiques.",12 "ABC99", targetDTO.getAccountId());

1 @Test 2 public void testGetCustomerOK() { 3 4 LOGGER.info("======= testGetCustomerOK starting..."); 5 6 try { 7 CustomerDTO targetDTO = this.serviceImpl.getCustomer("ABC99"); 8 9 // Vérifier.10 Assert.assertNotNull("Extra non trouvé.", targetDTO);11 Assert.assertEquals("Les accountId doivent être identiques.",12 "ABC99", targetDTO.getAccountId());13 14 } catch (CustomerBusinessException exception) {15 LOGGER.error("CustomerBusinessException : {}",16 exception.getCause());17 Assert.fail(exception.getMessage());18 } catch (UnavailableResourceException exception) {19 LOGGER.error("UnavailableResourceException : {}",20 exception.getMessage());21 Assert.fail(exception.getMessage());22 } catch (UnexpectedException exception) {23 LOGGER.error("UnexpectedException : {}" +24 exception.getMessage());25 Assert.fail(exception.getMessage());26 } catch (Exception exception) {27 LOGGER.error("CRASH : " + exception.getMessage());28 Assert.fail(exception.getMessage());29 }30 31 LOGGER.info("======= testGetCustomerOK done.");32 }

7 CustomerDTO targetDTO = this.serviceImpl.getCustomer("ABC99"); 8 9 // Vérifier.10 Assert.assertNotNull("Extra non trouvé.", targetDTO);11 Assert.assertEquals("Les accountId doivent être identiques.",12 "ABC99", targetDTO.getAccountId());

1 @Test 2 public void testGetCustomerOK() { 3 4 LOGGER.info("======= testGetCustomerOK starting..."); 5 6 try { 7 CustomerDTO targetDTO = this.serviceImpl.getCustomer("ABC99"); 8 9 // Vérifier.10 Assert.assertNotNull("Extra non trouvé.", targetDTO);11 Assert.assertEquals("Les accountId doivent être identiques.",12 "ABC99", targetDTO.getAccountId());13 14 } catch (CustomerBusinessException exception) {15 LOGGER.error("CustomerBusinessException : {}",16 exception.getCause());17 Assert.fail(exception.getMessage());18 } catch (UnavailableResourceException exception) {19 LOGGER.error("UnavailableResourceException : {}",20 exception.getMessage());21 Assert.fail(exception.getMessage());22 } catch (UnexpectedException exception) {23 LOGGER.error("UnexpectedException : {}" +24 exception.getMessage());25 Assert.fail(exception.getMessage());26 } catch (Exception exception) {27 LOGGER.error("CRASH : " + exception.getMessage());28 Assert.fail(exception.getMessage());29 }30 31 LOGGER.info("======= testGetCustomerOK done.");32 }

1 @Test 2 public void testGetCustomerOK() { 3 4 LOGGER.info("======= testGetCustomerOK starting..."); 5 6 try { 7 CustomerDTO targetDTO = this.serviceImpl.getCustomer("ABC99"); 8 9 // Vérifier.10 Assert.assertNotNull("Extra non trouvé.", targetDTO);11 Assert.assertEquals("Les accountId doivent être identiques.",12 "ABC99", targetDTO.getAccountId());13 14 } catch (CustomerBusinessException exception) {15 LOGGER.error("CustomerBusinessException : {}",16 exception.getCause());17 Assert.fail(exception.getMessage());18 } catch (UnavailableResourceException exception) {19 LOGGER.error("UnavailableResourceException : {}",20 exception.getMessage());21 Assert.fail(exception.getMessage());22 } catch (UnexpectedException exception) {23 LOGGER.error("UnexpectedException : {}" +24 exception.getMessage());25 Assert.fail(exception.getMessage());26 } catch (Exception exception) {27 LOGGER.error("CRASH : " + exception.getMessage());28 Assert.fail(exception.getMessage());29 }30 31 LOGGER.info("======= testGetCustomerOK done.");32 }

14 } catch (CustomerBusinessException exception) {15 LOGGER.error("CustomerBusinessException : {}",16 exception.getCause());17 Assert.fail(exception.getMessage());18 } catch (UnavailableResourceException exception) {19 LOGGER.error("UnavailableResourceException : {}",20 exception.getMessage());21 Assert.fail(exception.getMessage());22 } catch (UnexpectedException exception) {23 LOGGER.error("UnexpectedException : {}" +24 exception.getMessage());25 Assert.fail(exception.getMessage());26 } catch (Exception exception) {27 LOGGER.error("CRASH : " + exception.getMessage());28 Assert.fail(exception.getMessage());29 }30 31 LOGGER.info("======= testGetCustomerOK done.");

BRUIT

KISS !

1 @Test2 public void can_get_customer() throws Exception {3 CustomerDTO targetDTO = this.serviceImpl.getCustomer("ABC99");4 Assert.assertEquals("Les accountId doivent être identiques.",5 "ABC99", targetDTO.getAccountId());6 }

VENIR ARMÉ

JUNIT + MOCKITO + ASSERTJ

JUNIT + MOCKITO + ASSERTJ

Mock ?

simulacre

se fait passer pour ce qu’il n’est pas

comportement paramétrable

MOCKITO

doReturn(new BankAccount()).when(daoService).getObject("product", "id");

doReturn(new BankAccount()).when(daoService).getObject("product", "id");doReturn(new BankAccount())

doReturn(new BankAccount()).when(daoService).getObject("product", "id");when(daoService)

doReturn(new BankAccount()).when(daoService).getObject("product", "id"); getObject("product", "id");

doThrow(IOException.class).when(daoService).getObject("product", "id");

doThrow(IOException.class).when(daoService).getObject("product", "id");doThrow(IOException.class)

doThrow(IOException.class).when(daoService).getObject("product", "id");when(daoService)

doThrow(IOException.class).when(daoService).getObject("product", "id");getObject("product", "id");

ASSERTJ

assertThat(age).isGreaterThan(5);

assertThat(myList).containsExactly("MONAD42", "META18")

assertThat(negotation).isBooked();

NOUVEAU CODE = NOUVEAU TEST

NOUVEAU BUG = NOUVEAU TEST

nouveau bug = nouveau test

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

nouveau bug = nouveau test

6 System.err.println("Error with obj : " + obj.toString());

LE BUG EST ICI !

nouveau bug = nouveau test

1 @Test2 public void can_return_null_when_ioexception() throws IOException {3 Mockito.doThrow(IOException.class)4 .when(networkService).getObject("product", "azerty");5 6 Object product = service.getProduct("azerty");7 8 assertThat(product).isNull();9 }

3 Mockito.doThrow(IOException.class)4 .when(networkService).getObject("product", "azerty");

1 @Test2 public void can_return_null_when_ioexception() throws IOException {3 Mockito.doThrow(IOException.class)4 .when(networkService).getObject("product", "azerty");5 6 Object product = service.getProduct("azerty");7 8 assertThat(product).isNull();9 }

nouveau bug = nouveau test

1 @Test2 public void can_return_null_when_ioexception() throws IOException {3 Mockito.doThrow(IOException.class)4 .when(networkService).getObject("product", "azerty");5 6 Object product = service.getProduct("azerty");7 8 assertThat(product).isNull();9 }

6 Object product = service.getProduct("azerty");

nouveau bug = nouveau test

8 assertThat(product).isNull();

1 @Test2 public void can_return_null_when_ioexception() throws IOException {3 Mockito.doThrow(IOException.class)4 .when(networkService).getObject("product", "azerty");5 6 Object product = service.getProduct("azerty");7 8 assertThat(product).isNull();9 }

nouveau bug = nouveau test

nouveau bug = nouveau test

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

nouveau bug = nouveau test

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj : " + obj.toString());7 }8 return obj;9 }

nouveau bug = nouveau test

6 System.err.println("Error with obj : " + obj.toString());

LE BUG EST TOUJOURS ICI !

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj with id: " + id);7 }8 return obj;9 }

nouveau bug = nouveau test

6 System.err.println("Error with obj with id: " + id);

nouveau bug = nouveau test

1 public Object getProduct(String id) {2 Object obj = null;3 try {4 obj = networkService.getObject("product", id);5 } catch (IOException e) {6 System.err.println("Error with obj with id: " + id);7 }8 return obj;9 }

6 System.err.println("Error with obj with id: " + id);

Laissez votre câble tranquille

TESTER DOIT ÊTRE SIMPLE

code complexe 1 /** 2 * 3 * @param xml : document xml représentant le swap 4 * @return objet Swap 5 */ 6 public Swap parse(String xml) { 7 Swap swap = new Swap(); 8 String currency = getNodeValue("/swap/currency", xml); 9 swap.setCurrency(currency);[...] /* beaucoup de code... */ 530 Date d = new Date(); 531 if(test == 1) { 532 if(d.after(spotDate)) { 533 swap.setType("IRS"); 534 } else { 535 swap.setType("CURVE"); 536 } 537 } else if (test == 2) { 538 if(d.after(spotDate)) { 539 swap.setType("IRS"); 540 } else { 541 swap.setType("CURVE"); 542 } 543 }[...] /* encore beaucoup de code... */1135 return swap;1136 }

1 @Test 2 public void can_parse_xml() throws Exception { 3 String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 4 "<!--\n" + 5 "\t== Copyright (c) 2002-2005. All rights reserved.\n" + 6 "\t== Financial Products Markup Language is subject to the FpML public license.\n" + 7 "\t== A copy of this license is available at http://www.fpml.org/documents/license\n" + 8 "-->\n" + 9 "<FpML version=\"4-2\" xmlns=\"http://www.fpml.org/2005/FpML-4-2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.fpml.org/2005/FpML-4-2 fpml-main-4-2.xsd\" xsi:type=\"TradeCashflowsAsserted\">\n" +10 "\t<header>\n" +11 "\t\t<messageId messageIdScheme=\"http://www.example.com/messageId\">CEN/2004/01/05/15-38</messageId>\n" +12 "\t\t<sentBy>ABC</sentBy>\n" +13 "\t\t<sendTo>DEF</sendTo>\n" +14 "\t\t<creationTimestamp>2005-01-05T15:38:00-00:00</creationTimestamp>\n" +15 "\t</header>\n" +16 "\t<asOfDate>2005-01-05T15:00:00-00:00</asOfDate>\n" +17 "\t<tradeIdentifyingItems>\n" +18 "\t\t<partyTradeIdentifier>\n" +19 "\t\t\t<partyReference href=\"abc\"/>\n" +20 "\t\t\t<tradeId tradeIdScheme=\"http://www.abc.com/tradeId/\">trade1abcxxx</tradeId>\n" +21 "\t\t</partyTradeIdentifier>\n" +22 "\t\t<partyTradeIdentifier>\n" +23 "\t\t\t<partyReference href=\"def\"/>\n" +24 "\t\t\t<tradeId tradeIdScheme=\"http://www.def.com/tradeId/\">123cds</tradeId>\n" +25 "\t\t</partyTradeIdentifier>\n" +26 "\t</tradeIdentifyingItems>\n" +27 "\t<adjustedPaymentDate>2005-01-31</adjustedPaymentDate>\n" +28 "\t<netPayment>\n" +29 "\t\t<identifier netPaymentIdScheme=\"http://www.centralservice.com/netPaymentId\">netPaymentABCDEF001</identifier>\n" +30 "\t\t<payerPartyReference href=\"abc\"/>\n" +31 "\t\t<receiverPartyReference href=\"def\"/>\n" +32 "\t\t<paymentAmount>\n" +33 "\t\t\t<currency>EUR</currency>\n" +34 "\t\t\t<amount>200000</amount>\n" +35 "\t\t</paymentAmount>\n" +36 "\t</netPayment>\n" +

code complexe 1 /** 2 * 3 * @param xml : document xml représentant le swap 4 * @return objet Swap 5 */ 6 public Swap parse(String xml) { 7 Swap swap = new Swap(); 8 String currency = getNodeValue("/swap/currency", xml); 9 swap.setCurrency(currency);[...] /* beaucoup de code... */ 530 Date d = new Date(); 531 if(test == 1) { 532 if(d.after(spotDate)) { 533 swap.setType("IRS"); 534 } else { 535 swap.setType("CURVE"); 536 } 537 } else if (test == 2) { 538 if(d.after(spotDate)) { 539 swap.setType("IRS"); 540 } else { 541 swap.setType("CURVE"); 542 } 543 }[...] /* encore beaucoup de code... */1135 return swap;1136 }

1 /** 2 * 3 * @param xml : document xml représentant le swap 4 * @return objet Swap 5 */ 6 public Swap parse(String xml) { 7 Swap swap = new Swap(); 8 String currency = getNodeValue("/swap/currency", xml); 9 swap.setCurrency(currency);[...] /* beaucoup de code... */ 530 Date d = new Date(); 531 if(test == 1) { 532 if(d.after(spotDate)) { 533 swap.setType("IRS"); 534 } else { 535 swap.setType("CURVE"); 536 } 537 } else if (test == 2) { 538 if(d.after(spotDate)) { 539 swap.setType("IRS"); 540 } else { 541 swap.setType("CURVE"); 542 } 543 }[...] /* encore beaucoup de code... */1135 return swap;1136 }

code complexe 531 if(test == 1) { 532 if(d.after(spotDate)) { 533 swap.setType("IRS"); 534 } else { 535 swap.setType("CURVE"); 536 } 537 } else if (test == 2) { 538 if(d.after(spotDate)) { 539 swap.setType("IRS"); 540 } else { 541 swap.setType("CURVE"); 542 } 543 }

EXTRACT METHOD !

code complexe 1 public void updateSwapType(Swap swapToUpdate, Date now, Date spotDate, int test) { 2 if(test == 1) { 3 if(now.after(spotDate)) { 4 swapToUpdate.setType("IRS"); 5 } else { 6 swapToUpdate.setType("CURVE"); 7 } 8 } else if (test == 2) { 9 if(now.after(spotDate)) {10 swapToUpdate.setType("IRS");11 } else {12 swapToUpdate.setType("CURVE");13 }14 }15 }

code complexe

1 @Test2 public void can_update_swap_type() throws Exception {3 Swap swap = new Swap();4 Date now = simpleDateFormat.parse("2012-06-15");5 Date before = simpleDateFormat.parse("2012-05-05");6 service.updateSwapType(swap, now, before, 1);7 assertEquals("IRS", swap.getType());8 }

LE CODE DOIT ÊTRE MODULAIRE

Singleton 1 public class ProductService { 2 3 private static ProductService instance = new ProductService(); 4 5 private DAOService daoService; 6 7 private ProductService() { 8 System.out.println("ProductService constructor"); 9 daoService = DAOService.getInstance();10 }11 12 public static ProductService getInstance() {13 return instance;14 }15 16 17 18 public Object getProduct(String id) {19 return daoService.getObject("product", id);20 }21 }

1 public class ProductService { 2 3 private static ProductService instance = new ProductService(); 4 5 private DAOService daoService; 6 7 private ProductService() { 8 System.out.println("ProductService constructor"); 9 daoService = DAOService.getInstance();10 }11 12 public static ProductService getInstance() {13 return instance;14 }15 16 17 18 public Object getProduct(String id) {19 return daoService.getObject("product", id);20 }21 }

Singleton 3 private static ProductService instance = new ProductService();

1 public class ProductService { 2 3 private static ProductService instance = new ProductService(); 4 5 private DAOService daoService; 6 7 private ProductService() { 8 System.out.println("ProductService constructor"); 9 daoService = DAOService.getInstance();10 }11 12 public static ProductService getInstance() {13 return instance;14 }15 16 17 18 public Object getProduct(String id) {19 return daoService.getObject("product", id);20 }21 }

Singleton 7 private ProductService() { 9 daoService = DAOService.getInstance();10 }

Singleton 1 public class ProductService { 2 3 private static ProductService instance = new ProductService(); 4 5 private DAOService daoService; 6 7 private ProductService() { 8 System.out.println("ProductService constructor"); 9 daoService = DAOService.getInstance();10 }11 12 public static ProductService getInstance() {13 return instance;14 }15 16 17 18 public Object getProduct(String id) {19 return daoService.getObject("product", id);20 }21 }

Singleton

Code

Singleton

Code ProductService

Singleton

Code ProductService DAOService

Singleton

Code ProductService DAOService

Singleton

Code ProductService DAOService

Singleton

Code ProductService DAOService

MockProductService

Singleton

1 public void validateSwap(String id) {2 Swap swap = (Swap) ProductService.getInstance().getProduct(id);3 swap.updateState("VALIDATE");4 ProductService.getInstance().save(swap);5 }

1 public void validateSwap(String id) {2 Swap swap = (Swap) ProductService.getInstance().getProduct(id);3 swap.updateState("VALIDATE");4 ProductService.getInstance().save(swap);5 }

Singleton

ProductService.getInstance() 4 ProductService.getInstance()

COUPLAGE FORT

Injection

1 private ProductService productService; 2 3 public void setProductService(ProductService injectedService) { 4 this.productService = injectedService; 5 } 6 7 public void validateSwap(String id) { 8 Swap swap = (Swap) productService.getProduct(id); 9 swap.updateState("VALIDATE");10 productService.save(swap);11 }

1 private ProductService productService; 2 3 public void setProductService(ProductService injectedService) { 4 this.productService = injectedService; 5 } 6 7 public void validateSwap(String id) { 8 Swap swap = (Swap) productService.getProduct(id); 9 swap.updateState("VALIDATE");10 productService.save(swap);11 }

Injection

3 public void setProductService(ProductService injectedService) { 4 this.productService = injectedService; 5 }

INJECTION

1 private ProductService productService; 2 3 public void setProductService(ProductService injectedService) { 4 this.productService = injectedService; 5 } 6 7 public void validateSwap(String id) { 8 Swap swap = (Swap) productService.getProduct(id); 9 swap.updateState("VALIDATE");10 productService.save(swap);11 }

Injection

8 Swap swap = (Swap) productService.getProduct(id);

UTILISATION

Injection 1 @InjectMocks 2 private BankAccount bankAccount; 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);13 14 bankAccount.validateSwap("fakeId");15 16 assertEquals("VALIDATE", swap.getState());17 }

1 @InjectMocks 2 private BankAccount bankAccount; 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);13 14 bankAccount.validateSwap("fakeId");15 16 assertEquals("VALIDATE", swap.getState());17 }

Injection 4 @Mock 5 private ProductService productService;

MOCK

1 @InjectMocks 2 private BankAccount bankAccount; 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);13 14 bankAccount.validateSwap("fakeId");15 16 assertEquals("VALIDATE", swap.getState());17 }

Injection 1 @InjectMocks 2 private BankAccount bankAccount;

INJECTION AUTOMATIQUE DES MOCKS

1 @InjectMocks 2 private BankAccount bankAccount; 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);13 14 bankAccount.validateSwap("fakeId");15 16 assertEquals("VALIDATE", swap.getState());17 }

Injection 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);

1 @InjectMocks 2 private BankAccount bankAccount; 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);13 14 bankAccount.validateSwap("fakeId");15 16 assertEquals("VALIDATE", swap.getState());17 }

14 bankAccount.validateSwap("fakeId");

Injection

Injection 1 @InjectMocks 2 private BankAccount bankAccount; 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(swap).when(productService).getProduct("fakeId");12 Mockito.doNothing().when(productService).save(swap);13 14 bankAccount.validateSwap("fakeId");15 16 assertEquals("VALIDATE", swap.getState());17 }

16 assertEquals("VALIDATE", swap.getState());

YODA (C) DISNEY

UTILISE L’INJECTION, LUKE

YODA (C) DISNEY

Sans injecteur ?

1 public void validateSwap(String id) {2 Swap swap = (Swap) ProductService.getInstance().getProduct(id);3 swap.updateState("VALIDATE");4 ProductService.getInstance().save(swap);5 }

1 public void validateSwap(String id) {2 Swap swap = (Swap) ProductService.getInstance().getProduct(id);3 swap.updateState("VALIDATE");4 ProductService.getInstance().save(swap);5 }

Sans injecteur ?

ProductService.getInstance() 4 ProductService.getInstance()

EXTRACT METHOD !

1 public ProductService getProductService() {2 return ProductService.getInstance();3 }4 5 public void validateSwap(String id) {6 Swap swap = (Swap) getProductService().getProduct(id);7 swap.updateState("VALIDATE");8 getProductService().save(swap);9 }

Sans injecteur ?

1 public ProductService getProductService() {2 return ProductService.getInstance();3 } getProductService() 8 getProductService()

Sans injecteur ? 1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3); 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(productService).when(bankAccount).getProductService();12 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);15 16 bankAccount.validateSwap("fakeId");17 18 assertEquals("VALIDATE", swap.getState());19 }

1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3); 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(productService).when(bankAccount).getProductService();12 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);15 16 bankAccount.validateSwap("fakeId");17 18 assertEquals("VALIDATE", swap.getState());19 }

Sans injecteur ? 1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3);

REDÉFINITION DES MÉTHODESPOSSIBLE

1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3); 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(productService).when(bankAccount).getProductService();12 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);15 16 bankAccount.validateSwap("fakeId");17 18 assertEquals("VALIDATE", swap.getState());19 }

Sans injecteur ? 11 Mockito.doReturn(productService).when(bankAccount).getProductService();

1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3); 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(productService).when(bankAccount).getProductService();12 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);15 16 bankAccount.validateSwap("fakeId");17 18 assertEquals("VALIDATE", swap.getState());19 }

Sans injecteur ? 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);

Sans injecteur ? 1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3); 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(productService).when(bankAccount).getProductService();12 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);15 16 bankAccount.validateSwap("fakeId");17 18 assertEquals("VALIDATE", swap.getState());19 }

16 bankAccount.validateSwap("fakeId");

Sans injecteur ? 1 @Spy 2 private BankAccount bankAccount = new BankAccount("", 23, "", 3); 3 4 @Mock 5 private ProductService productService; 6 7 @Test 8 public void can_validate_swap() { 9 Swap swap = new Swap();10 11 Mockito.doReturn(productService).when(bankAccount).getProductService();12 13 Mockito.doReturn(swap).when(productService).getProduct("fakeId");14 Mockito.doNothing().when(productService).save(swap);15 16 bankAccount.validateSwap("fakeId");17 18 assertEquals("VALIDATE", swap.getState());19 }

18 assertEquals("VALIDATE", swap.getState());

Avertissement

LANCER UN TEST DOIT ÊTRE FACILE

Tests dans son IDEECLIPSE

NETBEANS

INTELLIJ

LE CODE DOIT TOUJOURS ÊTRE DÉPLOYABLE

Intégration continue

mvn install

Intégration continue

[INFO] ---------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ---------------------------------------------

Intégration continue

mvn install

Intégration continue

[INFO] ---------------------------------------------

[INFO] BUILD FAILURE

[INFO] ---------------------------------------------

Intégration continue

[INFO] ---------------------------------------------

[INFO] BUILD FAILURE

[INFO] ---------------------------------------------

WORKS ON

MY

MACHINE

Intégration continue

Déclenchement d’un build

Intégration continue

Déclenchement d’un build Compilation

Intégration continue

Déclenchement d’un build Compilation Test

Intégration continue

Déclenchement d’un build Compilation Test

Déploiement

Analyse de code

AVOIR LA BONNE COUVERTURE

80%DE COUVERTURE DE CODE

80%DE COUVERTURE DE GETTER/SETTER

TRAVAILLER EN BINÔME

binomage (pair hero)

philosophie différente pour code identique

HTTP://WWW.SXC.HU/PHOTO/959091

binomage (pair hero)

philosophie différente pour code identiqueLES COMMANDES

binomage (pair hero)

philosophie différente pour code identique

LE PLAN DE VOL

Ping Pong

J’écris le test

Il écrit l’implémentation

J’écris le test

Il écrit l’implémentation

bref

j’ai binômé

6 mois plus tard...

Bugs

0

22,5

45

67,5

90

Janvier Février Mars Avril Mai Juin

Bugs Satisfaction

Bugs

0

22,5

45

67,5

90

Janvier Février Mars Avril Mai Juin

Bugs Satisfaction

Bugs

0

22,5

45

67,5

90

Janvier Février Mars Avril Mai Juin

Bugs Satisfaction

Bugs

0

22,5

45

67,5

90

Janvier Février Mars Avril Mai Juin

Bugs Satisfaction

Bugs

0

22,5

45

67,5

90

Janvier Février Mars Avril Mai Juin

Bugs Satisfaction

Bugs

0

22,5

45

67,5

90

Janvier Février Mars Avril Mai Juin

Bugs Satisfaction

BRAVO À NOTRE ÉQUIPE DE CHOC !

(POUR RÉSUMER)

Installez Jenkins

Automatisez votre build

Écrivez votre 1er test

(même si il est «trop» simple)

HTTP://WWW.SXC.HU/PHOTO/664214

IL FAUT APPRENDRE À NAGER...

...AVANT DE GAGNER DES COMPÉTITIONS

HTTP://WWW.SXC.HU/PHOTO/1008962

( DEMO )

Merci !

It’s Pizza Time !Drink

@DWURSTEISEN

référence

jenkins : http://jenkins-ci.org/

assertj : https://github.com/joel-costigliola/assertj

mockito : https://code.google.com/p/mockito/

démo : http://parleys.com/play/5148922a0364bc17fc56c85a/about

Recommended