Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
JUnitで闘うレガシーコード改善
Search
YutaSaito
June 19, 2022
1k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
JUnitで闘うレガシーコード改善
JJUG CCC 2022 Springで発表した内容です。
YutaSaito
June 19, 2022
More Decks by YutaSaito
See All by YutaSaito
依存関係のテストだけじゃないArchUnitのこんな使い方
yutasaito
2
310
WireMockでHTTPをモックしよう
yutasaito
0
290
Featured
See All Featured
Ruling the World: When Life Gets Gamed
codingconduct
0
290
Speed Design
sergeychernyshev
33
2k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
610
Raft: Consensus for Rubyists
vanstee
141
7.6k
New Earth Scene 8
popppiees
3
2.5k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
990
Producing Creativity
orderedlist
PRO
348
40k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Designing for Performance
lara
611
70k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
430
Transcript
JUnitͰಆ͏ϨΨγʔίʔυվળ ++6($$$4QSJOH ᜊ౻༔ଠ!:VUB4BJUP
ࣗݾհ w ᜊ౻༔ଠ w αʔόαΠυΤϯδχΞڥඋͱ͔ͬͯΔ w ϨΨγʔίʔυͱಆ͖ͬͯ·ͨ͠ɻ ݱࡏ w
5XJUUFS!:VUB4BJUP
ηογϣϯ༰ w ༰ w +6OJUΛར༻ͯ͠ϨΨγʔίʔυΛվળ͢ΔͨΊʹࢲ͕औΓΜ Ͱ͍Δ͜ͱΛ͝հ͠·͢ɻ w ඪ w ʑͷۀͰϨΨγʔίʔυʹۤ࿑͍ͯ͠Δਓʹɺ+6OJUΛར༻
ͨ͠ϦϑΝΫλϦϯάΛͬͯΒ͏ w ٕज़ෛ࠴Λ૿͞ͳ͍ͨΊʹςετίʔυΛνʔϜͷจԽʹऔΓ ೖΕ͍ͨͱࢥͬͯΒ͏
ϨΨγʔίʔυͷྫ ࢲ͍͔ͭ͘ͷγεςϜͰҎԼͷΑ͏ͳίʔυΛݟΔ͜ͱ͕ ͋Γ·ͨ͠ɻ /** * ൢചརӹܭࢉ * * @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; }
ϨΨγʔίʔυͷྫ ࢲ͍͔ͭ͘ͷγεςϜͰҎԼͷΑ͏ͳίʔυΛݟΔ͜ͱ͕ ͋Γ·ͨ͠ɻ /** * ൢചརӹܭࢉ * * @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Λߋ৽͍ͯ͠Δ
ϨΨγʔίʔυʹ৽͍͠ػೳΛՃ͢Δ Ͱ͜ͷίʔυʹػೳΛՃͯ͠Έ·͢ɻ ࠓ͚ͩʮखྉແྉʯΩϟϯϖʔϯΛߦ͏ͱ͠·͢ɻ /** * ൢചརӹܭࢉ * * @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; }
৽͍͠ػೳΛՃ͢Δ खྉΛແྉʹ͢Δ͜͜Λͤྑ͍ͬΆ͍ /** * ൢചརӹܭࢉ * * @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; }
৽͍͠ػೳΛՃ͢Δ खྉܭࢉΛແྉʹ͢Δίʔυʹมߋ 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; } ݩͷίʔυ मਖ਼ޙͷίʔυ
৽͍͠ػೳΛՃ͢Δ ػೳվमͰ͖ͨʂ0,ʂ
৽͍͠ػೳΛՃ͢Δ ػೳվमͰ͖ͨʂ0,ʂ Μʁ ൢചརӹ͕ఆΑΓଟ͍ʁ
όάͷݪҼΛ֬ೝ͢Δ શ෦ͷίʔυΛݟͯΈ·͢ 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;
όάͷݪҼΛ֬ೝ͢Δ શ෦ͷίʔυΛݟͯΈ·͢ 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; खྉ͕ϚΠφεʹͳΔ
ϨΨγʔίʔυͱ ࠓճͷίʔυʹҎԼͷΑ͏ͳ͕͋Γ·ͨ͠ɻ ɾίʔυΛमਖ਼͢ΔͱଞͷՕॴʹ·ͰӨڹͯ͠͠·͏ ɾίʔυͷཧղʹ͕͔͔࣌ؒΔ Մಡੑ͕͍ ࢲ͕ؔΘ͖ͬͯͨγεςϜͰɺ͜ͷΑ͏ͳίʔυ͕͍ظ ؒଘࡏ͠ɺվળ͞Εͣʹܧଓͯ͠ར༻͞Ε͍ͯ·ͨ͠ɻ ͜ͷΑ͏ͳঢ়ଶΛϨΨγʔίʔυͱࠓճݺͼ·͢ɻ
ͦ͏ͩϦϑΝΫλϦϯά͠Α͏ʂ
ͲͷΑ͏ʹमਖ਼͢Δʁ ෭࡞༻Λͳͯ͘͠ݺͼग़͠ݩϝιου ϝιου ͰΫϥεͷΛઃఆ͞ ͍ͤͨͰ͢ɻҎԼमਖ਼ޙͷΠϝʔδ /** * ൢചརӹܭࢉ * *
@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; }
खಈͰͷςετ͍͠߹͋Δ ࠓճͷΑ͏ͳίʔυΛϦϑΝΫλϦϯάͯ͠ɺ σʔλͷ४උ͕େมɺܦ࿏͕ෳࡶͳͲͷཧ༝ͰखಈͰςετ ͢Δͷ͍͠߹͋Γɺόά͕ൃੜ͢ΔՄೳੑ͕͋Γ· ͢ɻ
खಈͰͷςετ͍͠߹͋Δ ࠓճͷΑ͏ͳίʔυΛϦϑΝΫλϦϯάͯ͠ɺ σʔλͷ४උ͕େมɺܦ࿏͕ෳࡶͳͲͷཧ༝ͰखಈͰςετ ͢Δͷ͍͠߹͋Γɺόά͕ൃੜ͢ΔՄೳੑ͕͋Γ· ͢ɻ +6OJUͰςετίʔυΛॻ͚ ҆શʹϦϑΝΫλϦϯάͰ͖·͢
ςετίʔυͷ࡞ ϦϑΝΫλϦϯάΛߦ͏લʹɺςετίʔυΛ࡞͠શͯ 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); }
ϦϑΝΫλϦϯά͢Δ ͰϦϑΝΫλϦϯά͠·͢ɻ /** * ൢചརӹܭࢉ * * @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; }
ςετίʔυΛ࣮ߦ ϦϑΝΫλϦϯάΛϛεΔͱςετΤϥʔ͕ൃੜ͠·͢
मਖ਼͢Δ ޡ͍ͬͯͨՕॴΛमਖ਼͠·͢ɻ /** * ൢചརӹܭࢉ * * @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; } ͕͜͜࿙Ε͍ͯͨ
ςετίʔυΛ࣮ߦ ςετΛ࣮ߦͯ͠શͯQBTTͨ͠ΒϦϑΝΫλϦϯάޭͰ ͢ɻ
͜ΕͰϨΨγʔίʔυΛվળ͍ͯͧ͘͠ʂ
͜ΕͰϨΨγʔίʔυΛվળ͍ͯͧ͘͠ʂ ͋Εʁվળ͍ͯ͠Δͣͳͷʹ ϨΨγʔίʔυશવݮΒͳ͍͔ʁ
ݸਓͷϦϑΝΫλϦϯάʹݶք͕͋Δ Ұਓ͕ςετΛ࡞ͯ͠ϦϑΝΫλϦϯάΛߦͳͬͯɺଞ ͷਓ͕৽ͨʹϨΨγʔίʔυΛ૿͢εϐʔυͷํ͕ૣ͍Մ ೳੑ͕͋Γ·͢ɻ ࠷ॳ෦ͷ૭ׂ͕Ε͍ͯΔ͚͕ͩͩͬͨɺ࣍ୈʹ͍͔ͭ͘ͷ෦ͷ૭ׂ͕Ε͍ͯΔঢ়ଶʹͳΔ ׂΕ૭ཧ
ςετίʔυΛॻ͘จԽΛ࡞Δ νʔϜͷଟ͘ͷਓ͕ςετίʔυΛॻ͘Α͏ʹͳΕ ɾόά͕গͳ͘ͳΔ ɾϦϑΝΫλϦϯά͘͢͠ͳΔ ɾྑ͍ίʔυ͕૿͑Δ ϨΨγʔίʔυͷ૿Ճεϐʔυྑ͍ίʔυͷ૿Ճεϐʔυ ʹͳͬͯγεςϜ͕վળ͞Ε͍͖ͯ·͢
Ͳ͏ͬͯςετίʔυจԽΛ࡞Δʁ ࢲνʔϜʹςετίʔυΛॻ͘จԽΛ࡞ΔͨΊʹ ҎԼͷΑ͏ͳ͜ͱΛ࣮ࢪ͠·ͨ͠ɻ ɾڧ੍తʹςετίʔυΛҙࣝ͢Δڥͷඋ ɾςετίʔυ͕ޮՌతʹॻ͚͍ͯΔ͔ΛՄࢹԽ ɾςετίʔυ࡞Λָʹ͢ΔϥΠϒϥϦͷಋೖ
Ͳ͏ͬͯςετίʔυจԽΛ࡞Δʁ ࢲνʔϜʹςετίʔυΛॻ͘จԽΛ࡞ΔͨΊʹ ҎԼͷΑ͏ͳ͜ͱΛ࣮ࢪ͠·ͨ͠ɻ ɾڧ੍తʹςετίʔυΛҙࣝ͢Δڥͷඋ ɾςετίʔυ͕ޮՌతʹॻ͚͍ͯΔ͔ΛՄࢹԽ ɾςετίʔυ࡞Λָʹ͢ΔϥΠϒϥϦͷಋೖ
$*ͷಋೖ σϓϩΠϑϩʔʹ$* ςετ࣮ߦ Λಋೖ͠ɺ ςετʹࣦഊͨ͠ΒΤϥʔʹͯ͠σϓϩΠͤ͞ͳ͍͜ͱͰ ڧ੍తʹςετΛҙࣝ͢Δঢ়ଶΛ࡞Γ·ͨ͠ɻ ίϛοτ Ϗϧυ ςετ σϓϩΠ
ಛఆͷϒϥϯνʹϚʔδ͞Εͨࡍʹ ࣗಈͰςετ͢Δ
$*ͷಋೖ (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λεΫ͕࣮ߦ͞ΕΔ
Ͳ͏ͬͯςετίʔυจԽΛ࡞Δʁ ࢲνʔϜʹςετίʔυΛॻ͘จԽΛ࡞ΔͨΊʹ ҎԼͷΑ͏ͳ͜ͱΛ࣮ࢪ͠·ͨ͠ɻ ɾڧ੍తʹςετίʔυΛҙࣝ͢Δڥͷඋ ɾςετίʔυ͕ޮՌతʹॻ͚͍ͯΔ͔ΛՄࢹԽ ɾςετίʔυ࡞Λָʹ͢ΔϥΠϒϥϦͷಋೖ
ΧόϨοδΛՄࢹԽ ΧόϨοδΛՄࢹԽ͢Δ͜ͱͰ ޮՌతʹςετίʔυ͕ॻ͚͍ͯΔ͔͕͔Γɺ ·ͨͱͯ݁͠Ռ͕ग़Δ͜ͱͰϞνϕʔγϣϯʹܨ͕Δ͔ ͱߟ͍͑ͯ·͢ɻ ҎԼͰ+B$P$PͰग़ྗͨ͠ϨϙʔτΛද͍ࣔͯ͠·͢ɻ
+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
Ͳ͏ͬͯςετίʔυจԽΛ࡞Δʁ ࢲνʔϜʹςετίʔυΛॻ͘จԽΛ࡞ΔͨΊʹ ҎԼͷΑ͏ͳ͜ͱΛ࣮ࢪ͠·ͨ͠ɻ ɾڧ੍తʹςετίʔυΛҙࣝ͢Δڥͷඋ ɾςετίʔυ͕ޮՌతʹॻ͚͍ͯΔ͔ΛՄࢹԽ ɾςετίʔυ࡞Λָʹ͢ΔϥΠϒϥϦͷಋೖ
ςετίʔυಋೖͷ՝ ɾॻ͍ͨ͜ͱ͕ͳ͍ਓͷ৺ཧతϋʔυϧ ɾϨΨγʔίʔυʹςετίʔυΛॻ͘ͷ͕େม ςετ͕ఆ͞ΕͨίʔυͰͳ͍ͨΊςετίʔυͷ࡞ʹ͕͔͔࣌ؒΔ ։ൃʹ͔͔࣌ؒΔ ΊΜͲͦ͘͞͏
ςετίʔυಋೖͷ՝ ɾॻ͍ͨ͜ͱ͕ͳ͍ਓͷ৺ཧతϋʔυϧ ɾϨΨγʔίʔυʹςετίʔυΛॻ͘ͷ͕େม ςετ͕ఆ͞ΕͨίʔυͰͳ͍ͨΊςετίʔυͷ࡞ʹ͕͔͔࣌ؒΔ ։ൃʹ͔͔࣌ؒΔ ΊΜͲͦ͘͞͏ ָΛ͢ΔͨΊʹϥΠϒϥϦΛಋೖ͠Α͏ʂ ςετϥΠϒϥϦͷಋೖͰ͋ΕɺϓϩμΫγϣϯίʔυ ͷӨڹݶఆతͰൺֱతؾܰʹಋೖͰ͖·͢
ϞοΫΛ͏ %#ૢ࡞ɺෳࡶͳϝιουΛݺͼग़͢ͳͲςετίʔυΛॻ ͘ͷʹۤ࿑͢ΔίʔυϞοΫΛར༻͢Ε؆୯ʹςετՄ ೳʹͳΓ·͢ɻ /** * ׂҾֹۚͷܭࢉ * * @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; }
ϞοΫΛ͏ ϞοΫΛར༻ͨ͠ςετ͜ͷΑ͏ͳܗʹͳΓ·͢ɻ .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); } } ϝιουͷΓΛϞοΫԽ
ΞαʔγϣϯϥΠϒϥϦΛ͏ +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ϝιουΛ༻ҙ͍ͯ͠ͳ͍
ΞαʔγϣϯϥΠϒϥϦΛ͏ +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ϝιουΛ༻ҙ͍ͯ͠ͳ͍
·ͱΊ w +6OJUΛ͑҆શʹϦϑΝΫλϦϯάͰ͖Δ w ݸਓͰͷϦϑΝΫλϦϯάʹݶք͕͋ΔͨΊςετίʔυΛॻ ͘จԽΛ࡞Δ w $*Λಋೖͯ͠ςετίʔυΛڧ੍తʹҙࣝͤ͞Δ w ΧόϨοδΛՄࢹԽͯ͠ޮՌతʹςετίʔυ͕ॻ͚͍ͯΔ͔
֬ೝͰ͖ΔڥΛ࡞Δ w ςετίʔυΛָʹ͢ΔϥΠϒϥϦΛಋೖͯ͠ɺςετίʔυ ࡞ͷϋʔυϧΛԼ͛Δ
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ