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

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

YutaSaito
June 19, 2022
820

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

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

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ϝιουΛ༻ҙ͍ͯ͠ͳ͍