Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JUnitで闘うレガシーコード改善

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for YutaSaito YutaSaito
June 19, 2022
970

 JUnitで闘うレガシーコード改善

JJUG CCC 2022 Springで発表した内容です。

Avatar for YutaSaito

YutaSaito

June 19, 2022
Tweet

Transcript

  1. ϨΨγʔίʔυͷྫ ࢲ͸͍͔ͭ͘ͷγεςϜͰҎԼͷΑ͏ͳίʔυΛݟΔ͜ͱ͕ ͋Γ·ͨ͠ɻ /** * ൢചརӹܭࢉ * * @param price

    ঎඼୅ۚ * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; sales.profit = price; // ख਺ྉܭࢉ feeCalculate(sales); // ഑ૹྉܭࢉ deliveryFeeCalculate(sales, shippingSize); return sales; }
  2. ϨΨγʔίʔυͷྫ ࢲ͸͍͔ͭ͘ͷγεςϜͰҎԼͷΑ͏ͳίʔυΛݟΔ͜ͱ͕ ͋Γ·ͨ͠ɻ /** * ൢചརӹܭࢉ * * @param price

    ঎඼୅ۚ * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; sales.profit = price; // ख਺ྉܭࢉ feeCalculate(sales); // ഑ૹྉܭࢉ deliveryFeeCalculate(sales, shippingSize); return sales; } ϝιουʹTBMFTΛ౉ͯ͠ ͜ͷϝιουͷதͰTBMFTΛߋ৽͍ͯ͠Δ
  3. ϨΨγʔίʔυʹ৽͍͠ػೳΛ௥Ճ͢Δ Ͱ͸͜ͷίʔυʹػೳΛ௥Ճͯ͠Έ·͢ɻ ࠓ͚ͩʮख਺ྉແྉʯΩϟϯϖʔϯΛߦ͏ͱ͠·͢ɻ /** * ൢചརӹܭࢉ * * @param price

    ঎඼୅ۚ * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; sales.profit = price; // ख਺ྉܭࢉ feeCalculate(sales); // ഑ૹྉܭࢉ deliveryFeeCalculate(sales, shippingSize); return sales; }
  4. ৽͍͠ػೳΛ௥Ճ͢Δ ख਺ྉΛແྉʹ͢Δ͸͜͜Λ௚ͤ͹ྑ͍ͬΆ͍ /** * ൢചརӹܭࢉ * * @param price ঎඼୅ۚ

    * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; sales.profit = price; // ख਺ྉܭࢉ feeCalculate(sales); // ഑ૹྉܭࢉ deliveryFeeCalculate(sales, shippingSize); return sales; }
  5. ৽͍͠ػೳΛ௥Ճ͢Δ ख਺ྉܭࢉΛແྉʹ͢Δίʔυʹมߋ private void feeCalculate(Sales sales) { int rate; if

    (sales.price < 5_000) { rate = 20; } else if (sales.price < 10_000) { rate = 10; } else { rate = 5; } int fee = sales.price * rate / 100; sales.fee = fee; sales.profit = sales.profit - fee; } private void feeCalculate(Sales sales) { sales.fee = 0; } ݩͷίʔυ मਖ਼ޙͷίʔυ
  6. όάͷݪҼΛ֬ೝ͢Δ શ෦ͷίʔυΛݟͯΈ·͢ private void feeCalculate(Sales sales) { sales.fee = 0;

    } private void deliveryFeeCalculate(Sales sales, ShippingSize shippingSize) { int deliveryFee = switch (shippingSize) { case SMALL -> 100; case MEDIUM -> 200; case LARGE -> 300; }; // ഑ૹྉ͕300ԁҎ্ͳΒख਺ྉ͔Β100ԁׂҾ if (deliveryFee >= 300) { sales.fee -= 100; sales.profit += 100; } sales.deliveryFee = deliveryFee; sales.profit = sales.profit - deliveryFee; } // ख਺ྉܭࢉ feeCalculate(sales); // ഑ૹྉܭࢉ deliveryFeeCalculate(sales, shippingSize); return sales;
  7. όάͷݪҼΛ֬ೝ͢Δ શ෦ͷίʔυΛݟͯΈ·͢ private void feeCalculate(Sales sales) { sales.fee = 0;

    } private void deliveryFeeCalculate(Sales sales, ShippingSize shippingSize) { int deliveryFee = switch (shippingSize) { case SMALL -> 100; case MEDIUM -> 200; case LARGE -> 300; }; // ഑ૹྉ͕300ԁҎ্ͳΒख਺ྉ͔Β100ԁׂҾ if (deliveryFee >= 300) { sales.fee -= 100; sales.profit += 100; } sales.deliveryFee = deliveryFee; sales.profit = sales.profit - deliveryFee; } // ख਺ྉܭࢉ feeCalculate(sales); // ഑ૹྉܭࢉ deliveryFeeCalculate(sales, shippingSize); return sales; ख਺ྉ͕ϚΠφεʹͳΔ
  8. ͲͷΑ͏ʹमਖ਼͢Δʁ ෭࡞༻Λͳͯ͘͠ݺͼग़͠ݩϝιου ౰ϝιου ͰΫϥεͷ஋Λઃఆ͞ ͍ͤͨͰ͢ɻҎԼ͸मਖ਼ޙͷΠϝʔδ /** * ൢചརӹܭࢉ * *

    @param price ঎඼୅ۚ * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; // ख਺ྉܭࢉ int fee = feeCalculate(sales.price); // ഑ૹྉܭࢉ int deliveryFee = deliveryFeeCalculate(shippingSize); // ഑ૹྉ͕300ԁҎ্ͳΒख਺ྉ͔Β100ԁׂҾ if (deliveryFee >= 300) { fee = max(fee - 100, 100); } sales.fee = fee; sales.deliveryFee = deliveryFee; sales.profit = price - fee - deliveryFee; return sales; }
  9. ςετίʔυͷ࡞੒ ϦϑΝΫλϦϯάΛߦ͏લʹɺςετίʔυΛ࡞੒͠શͯ QBTT͢Δ͜ͱΛ֬ೝ͠·͢ɻ @Test void ഑ૹྉ͕300ԁ() { Sales actual =

    new SalesService().profitCalculate(1_000, ShippingSize.LARGE); assertEquals(1_000, actual.price); assertEquals(100, actual.fee); assertEquals(300, actual.deliveryFee); assertEquals(600, actual.profit); }
  10. ϦϑΝΫλϦϯά͢Δ Ͱ͸ϦϑΝΫλϦϯά͠·͢ɻ /** * ൢചརӹܭࢉ * * @param price ঎඼୅ۚ

    * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; // ख਺ྉܭࢉ int fee = feeCalculate(sales.price); // ഑ૹྉܭࢉ int deliveryFee = deliveryFeeCalculate(shippingSize); sales.fee = fee; sales.deliveryFee = deliveryFee; sales.profit = price - fee - deliveryFee; return sales; }
  11. मਖ਼͢Δ ޡ͍ͬͯͨՕॴΛमਖ਼͠·͢ɻ /** * ൢചརӹܭࢉ * * @param price ঎඼୅ۚ

    * @param shippingSize ঎඼αΠζ * @return ച্ */ public Sales profitCalculate(int price, ShippingSize shippingSize) { if (price < 1_000) { throw new IllegalArgumentException("priceʹ͸1,000ԁҎ্Λઃఆ͍ͯͩ͘͠͞ɻ"); } Sales sales = new Sales(); sales.price = price; // ख਺ྉܭࢉ int fee = feeCalculate(sales); // ഑ૹྉܭࢉ int deliveryFee = deliveryFeeCalculate(sales, shippingSize); // ഑ૹྉ͕300ԁҎ্ͳΒख਺ྉ͔Β100ԁׂҾ if (deliveryFee >= 300) { fee = max(fee - 100, 100); } sales.fee = fee; sales.deliveryFee = deliveryFee; sales.profit = price - fee - deliveryFee; return sales; } ͕͜͜࿙Ε͍ͯͨ
  12. $*ͷಋೖ (SBEMF΍.BWFOͰ$*Λಋೖ͢Δͷʹಛผͳઃఆ͸ඞཁͳ͠ ςετίʔυ͑͋͞Ε͹ɺCVJMEͱಉ࣌ʹςετ΋࣮ߦ͞Ε ·͢ɻ $ ./gradlew --console=plain build > Task

    :compileJava UP-TO-DATE > Task :processResources UP-TO-DATE > Task :classes UP-TO-DATE > Task :bootJarMainClassName UP-TO-DATE > Task :bootJar UP-TO-DATE > Task :jar UP-TO-DATE > Task :assemble UP-TO-DATE > Task :spotlessInternalRegisterDependencies UP-TO-DATE > Task :spotlessJava UP-TO-DATE > Task :spotlessJavaCheck UP-TO-DATE > Task :spotlessCheck UP-TO-DATE > Task :compileTestJava UP-TO-DATE > Task :processTestResources NO-SOURCE > Task :testClasses UP-TO-DATE > Task :test UP-TO-DATE > Task :check UP-TO-DATE > Task :build UP-TO-DATE BUILD SUCCESSFUL in 1s 10 actionable tasks: 10 up-to-date UFTUλεΫ͕࣮ߦ͞ΕΔ
  13. +B$P$Pͷಋೖ (SBEMF΍.BWFOʹ͸+B$P$Pͷ1MVHJOΛ௥Ճͯ͋͛͠Ε͹ ར༻ՄೳͰ͢ɻৄࡉ͸ϦϯΫΛ͝ཡ͍ͩ͘͞ɻ plugins { id 'jacoco' } jacocoTestReport {

    dependsOn test reports { xml.required = false csv.required = false html.required = true } } <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.9-SNAPSHOT</version> </plugin> <project> <reporting> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <reportSets> <reportSet> <reports> <report>report</report> </reports> </reportSet> </reportSets> </plugin> </plugins> </reporting> </project> CVJMEHSBEMF QPNYNM IUUQTEPDTHSBEMFPSHDVSSFOUVTFSHVJEFKBDPDP@QMVHJOIUNM IUUQTXXXKBDPDPPSHKBDPDPUSVOLEPDNBWFOIUNM
  14. ϞοΫΛ࢖͏ %#ૢ࡞ɺෳࡶͳϝιουΛݺͼग़͢ͳͲςετίʔυΛॻ ͘ͷʹۤ࿑͢Δίʔυ΋ϞοΫΛར༻͢Ε͹؆୯ʹςετՄ ೳʹͳΓ·͢ɻ /** * ׂҾֹۚͷܭࢉ * * @param

    userId * @return ׂҾֹۚ */ public int discount(String userId) { LocalDate toDate = localDateService.now(); LocalDate fromDate = toDate.minusDays(7); List<Purchase> purchases = purchaseRepository.findByPurchaseDateBetween(userId, fromDate, toDate); if (purchases.size() >= 5) { return 200; } if (purchases.size() >= 3) { return 100; } return 0; }
  15. ϞοΫΛ࢖͏ ϞοΫΛར༻ͨ͠ςετ͸͜ͷΑ͏ͳܗʹͳΓ·͢ɻ .PDLJUPΛར༻ͨ͠ྫ class DiscountServiceTest { @Mock private LocalDateService localDateService;

    @Mock private PurchaseRepository purchaseRepository; @InjectMocks private DiscountService discountService; @BeforeEach void beforeEach() { MockitoAnnotations.openMocks(this); } @Test void औҾ݅਺͕5݅ͷ৔߹͸200ԁׂҾ() { String userId = "123"; LocalDate toDate = LocalDate.of(2022, 6, 13); when(localDateService.now()).thenReturn(toDate); LocalDate fromDate = LocalDate.of(2022, 6, 6); List<Purchase> purcases = IntStream.range(1, 6).mapToObj(i -> new Purchase(toDate, 100 * i)).toList(); when(purchaseRepository.findByPurchaseDateBetween(userId, fromDate, toDate)) .thenReturn(purcases); int actual = discountService.discount(userId); assertEquals(200, actual); verify(localDateService).now(); verify(purchaseRepository).findByPurchaseDateBetween(userId, fromDate, toDate); } } ϝιουͷ໭Γ஋ΛϞοΫԽ
  16. ΞαʔγϣϯϥΠϒϥϦΛ࢖͏ +6OJUʹσϑΥϧτͰ༻ҙ͞Ε͍ͯΔΞαʔγϣϯͩͱςε τίʔυͷ࡞੒͕ෳࡶʹͳΔ͜ͱ͕͋Γ·͢ɻ List<Person> actual = personService.ranking(); List<Person> taroFilterdActual =

    actual.stream() .filter(person -> person.getName().equals("taro") && person.getAge() == 20) .toList(); assertEquals(1, taroFilterdActual.size()); List<Person> hanakoFilterdActual = actual.stream() .filter(person -> person.getName().equals("hanako") && person.getAge() == 25) .toList(); assertEquals(1, hanakoFilterdActual.size()); 1FSTPOΫϥε͸ FRVBMTϝιουΛ༻ҙ͍ͯ͠ͳ͍
  17. ΞαʔγϣϯϥΠϒϥϦΛ࢖͏ +6OJUʹσϑΥϧτͰ༻ҙ͞Ε͍ͯΔΞαʔγϣϯͩͱςε τίʔυͷ࡞੒͕ෳࡶʹͳΔ͜ͱ͕͋Γ·͢ɻ List<Person> actual = personService.ranking(); List<Person> taroFilterdActual =

    actual.stream() .filter(person -> person.getName().equals("taro") && person.getAge() == 20) .toList(); assertEquals(1, taroFilterdActual.size()); List<Person> hanakoFilterdActual = actual.stream() .filter(person -> person.getName().equals("hanako") && person.getAge() == 25) .toList(); assertEquals(1, hanakoFilterdActual.size()); List<Person> actual = personService.ranking(); Person taro = new Person(0L, "taro", 20); Person hanako = new Person(0L, "hanako", 25); assertThat(actual) .usingRecursiveFieldByFieldElementComparatorOnFields("name", "age") .contains(taro, hanako); ΞαʔγϣϯϥΠϒϥϦΛར༻͢Δ͜ͱͰ؆୯ʹॻ͚ΔՄೳੑ͕͋Γ·͢ɻ ҎԼ͸"TTFSU+Λར༻ͨ͠ྫͰ͢ɻ 1FSTPOΫϥε͸ FRVBMTϝιουΛ༻ҙ͍ͯ͠ͳ͍