Upload
soat
View
1.339
Download
0
Embed Size (px)
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
16 mai 13
Test unitaire ? Mock ? TDD ?
Kezako ?
1
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 !
HTTP://UPLOAD.WIKIMEDIA.ORG/WIKIPEDIA/COMMONS/THUMB/6/69/HUMAN_EVOLUTION.SVG/1000PX-HUMAN_EVOLUTION.SVG.PNG
LEGACY CODE
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 ?
HTTP://UPLOAD.WIKIMEDIA.ORG/WIKIPEDIA/COMMONS/THUMB/6/69/HUMAN_EVOLUTION.SVG/1000PX-HUMAN_EVOLUTION.SVG.PNG
TEST DRIVEN DEVELOPMENT
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 }
HTTP://UPLOAD.WIKIMEDIA.ORG/WIKIPEDIA/COMMONS/THUMB/6/69/HUMAN_EVOLUTION.SVG/1000PX-HUMAN_EVOLUTION.SVG.PNG
EN PRATIQUE
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 )
HTTP://UPLOAD.WIKIMEDIA.ORG/WIKIPEDIA/COMMONS/THUMB/6/69/HUMAN_EVOLUTION.SVG/1000PX-HUMAN_EVOLUTION.SVG.PNG
QUESTIONS ?
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