Backend Language/TDD

[TDD Chapter3] ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± μˆœμ„œ

chaerlo127 2023. 1. 12. 16:05
728x90

πŸ’› ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± μˆœμ„œ

1. μ‰¬μš΄ κ²½μš°μ—μ„œ μ–΄λ €μš΄ 경우둜 진행
2. μ˜ˆμ™Έμ μΈ κ²½μš°μ—μ„œ μ •μƒμ μœΌλ‘œ 진행

κ·Έλ ‡λ‹€λ©΄ μ–΄λ €μš΄ 경우둜 μž‘μ„±ν•˜λ©΄ μ•ˆλ˜λŠ” μ΄μœ λŠ” 뭘까?


🌱 μ΄ˆλ°˜μ— λ³΅μž‘ν•œ ν…ŒμŠ€νŠΈλΆ€ν„° μ‹œμž‘ν•˜λ©΄ μ•ˆλ˜λŠ” 이유

μ΄ˆλ°˜μ— λ³΅μž‘ν•˜κ²Œ μ‹œμž‘ν•˜λ©΄ ν•œ λ²ˆμ— κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” μ½”λ“œκ°€ λ§Žμ•„μ§„λ‹€.

if 문이 λŠ˜μ–΄λ‚  μˆ˜λ„ 있고 ν•œλ²ˆμ— λ§Žμ€ μ½”λ“œλ₯Ό λ§Œλ“€λ‹€λ³΄λ©΄ λ‚˜λ„ λͺ¨λ₯΄κ²Œ 버그λ₯Ό λ§Œλ“€κ³  λ‚˜μ€‘μ— 버그λ₯Ό 작기 μœ„ν•΄ λ§Žμ€ μ‹œκ°„μ„ ν—ˆλΉ„ν•˜κ²Œ λœλ‹€.

 


🌱 κ΅¬ν˜„ν•˜κΈ° μ‰¬μš΄ ν…ŒμŠ€νŠΈλΆ€ν„° μ‹œμž‘ν•˜κΈ°

보톡 μˆ˜λΆ„ 내에 κ΅¬ν˜„μ„ μ™„λ£Œν•˜κ³  톡과할 수 μžˆλŠ” ν…ŒμŠ€νŠΈ κ΅¬ν˜„μ„ 선택해야 ν•œλ‹€.

 

Chapter 2μ—μ„œ ν–ˆλ˜ μ•”ν˜Έ 강도 μΈ‘μ • μ˜ˆμ—μ„œλŠ” λ‹€μŒ 쀑 ν•˜λ‚˜κ°€ μ‰¬μšΈ 것이닀.

1. λͺ¨λ“  μ‘°κ±΄μ—μ„œ μΆ©μ‘±ν•˜λŠ” 경우
2. λͺ¨λ“  μ‘°κ±΄μ—μ„œ μΆ©μ‘±ν•˜μ§€ μ•ŠλŠ” 경우

1번의 경우 λͺ¨λ“  쑰건을 μΆ©μ‘±ν•˜μ§€ μ•ŠλŠ” 경우라면 λ‹¨μˆœνžˆ WEAKλ₯Ό return ν•˜λ©΄ 되고, 2번의 경우 λͺ¨λ“  쑰건을 μΆ©μ‘±ν•˜λŠ” κ²½μš°μ—λŠ” λ‹¨μˆœνžˆ STRONG을 return ν•˜λ©΄ λœλ‹€. 

 

1λ²ˆμ„ μ„ νƒν•˜κ³  ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±μ„ μ΄μ–΄λ‚˜κ°„λ‹€κ³  κ°€μ •ν•˜μž.

 

그럼 λ‹€μŒκ³Ό 같은 경우의 μˆ˜κ°€ μžˆμ„ 것이닀.

1. λͺ¨λ“  κ·œμΉ™μ„ μΆ©μ‘±ν•˜μ§€ μ•ŠλŠ” 경우
2. ν•œ κ·œμΉ™λ§Œ μΆ©μ‘±ν•˜λŠ” 경우
3. 두 κ·œμΉ™μ„ μΆ©μ‘±ν•˜λŠ” 경우 
1번: λͺ¨λ“  κ·œμΉ™μ„ μΆ©μ‘±ν•˜λŠ” 경우둜 μ½”λ“œλ₯Ό μƒμ„±ν–ˆκΈ° λ•Œλ¬Έμ— μ˜ˆμ™Έμ²˜λ¦¬κ°€ λ§Žμ•„μ Έ 버그 생성
2번: ν•œ κ·œμΉ™μ„ μΆ©μ‘±ν•˜λŠ”μ§€ μ—¬λΆ€λ₯Ό 체크
3번: ν•œ κ·œμΉ™μ„ μΆ©μ‘±ν•˜λŠ”μ§€ κ²€μ‚¬ν•˜κ³ , μΆ©μ‘±ν•˜μ§€ μ•ŠλŠ” κ·œμΉ™μ΄ 있기 λ•Œλ¬Έμ— λ‘˜ λ‹€ 확인 ν›„ NORMAL을 return 

λ”°λΌμ„œ, 2번과 3번 μ‚¬μ΄μ—μ„œ 선택

κ·œμΉ™ μ€‘μ—μ„œλŠ”

1. κΈ€μž 수 μΆ©μ‘±
2. λŒ€λ¬Έμž μ—¬λΆ€ 검사
3. 숫자 포함 μ—¬λΆ€ 검사

κ°€ μžˆμ„ 것이고 κ·Έ μ€‘μ—μ„œ μ‰¬μšΈ κ·œμΉ™μ„ μ„ νƒν•˜μ—¬ μ½”λ“œλ₯Ό μ„€κ³„ν•œλ‹€. 


🌱 μ˜ˆμ™Έ 상황을 λ¨Όμ € ν…ŒμŠ€νŠΈν•΄μ•Ό ν•˜λŠ” 이유

μ˜ˆμ™Έ 사항을 μ „ν˜€ κ³ λ €ν•˜μ§€ μ•Šμ€ μ½”λ“œμ—μ„œ μ˜ˆμ™Έ 상황을 λ°˜μ˜ν•˜λ €λ©΄ μ½”λ“œμ˜ ꡬ쑰λ₯Ό λ’€μ§‘κ±°λ‚˜ 쀑간에 μ˜ˆμ™Έ 상황을 μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ 쑰건문을 μ€‘λ³΅ν•΄μ„œ μΆ”κ°€λ₯Ό ν•΄μ•Όν•œλ‹€. 

 

μ΄ˆλ°˜μ— μ˜ˆμ™Έ 상황을 ν…ŒμŠ€νŠΈν•˜λ©΄ μœ„μ™€ 같은 상황이 λ°œμƒν•  일이 쀄어든닀.

 

예λ₯Ό λ“€λ©΄ 2 μ°¨μ‹œμ—μ„œ μ§„ν–‰ν•œ μ½”λ“œμ—μ„œ 값이 없을 λ•Œ λ°œμƒν•˜λŠ” NPEλ₯Ό 사전에 막을 수 μžˆλ‹€λŠ” 점이닀. 

NPEλŠ” μ—λŸ¬ λ°œμƒλΏλ§Œ μ•„λ‹ˆλΌ ν”„λ‘œμ„ΈμŠ€λ₯Ό 죽일 μˆ˜λ„ 있기 λ•Œλ¬Έμ΄λ‹€. 


🌱 μ™„κΈ‰ 쑰절

μ²˜μŒμ— ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± μ‹œ, μ–΄λ €μš΄ 것 쀑 ν•˜λ‚˜κ°€ ν•œ λ²ˆμ— μ–Όλ§ˆλ§ŒνΌ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 것인가이닀.

TDD λ₯Ό 처음 μ ‘ν•  λ•Œμ—λŠ” λ‹€μŒ λ‹¨κ³„λ‘œ κ±°μ³μ„œ μ—°μŠ΅ν•˜μž!

 

1. 정해진 값을 return
2. κ°’ 비ꡐλ₯Ό μ΄μš©ν•΄μ„œ 정해진 값을 return
3. λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈλ₯Ό μΆ”κ°€ν•˜λ©΄μ„œ κ΅¬ν˜„μ„ μΌλ°˜ν™”
public class PasswordStrengthMeterTest {
    @Test
    void meetsOtherCreteria_except_for_length_Then_Normal(){
        testdriven.chap03.PasswordStrengthMeter meter = new PasswordStrengthMeter();
        PasswordStrength result = meter.meter("ab12!@A");
        assertEquals(PasswordStrength.NORMAL, result);
        PasswordStrength result2 = meter.meter("Ab12!c");
        assertEquals(PasswordStrength.NORMAL, result2);
    }

}

////////////////////////
    public PasswordStrength meter(String s){
//        if("ab12!@A".equals(s) || "Ab12!c".equals(s)) return PasswordStrength.NORMAL;
        if(s.length()<8) return PasswordStrength.NORMAL;
        else return PasswordStrength.STRONG;
    }

🌱 지속적인 λ¦¬νŒ©ν† λ§

ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όλœ ν›„μ—λŠ” λ¦¬νŒ©ν† λ§μ„ μ§„ν–‰ν•œλ‹€.

맀번 λ¦¬νŽ™ν† λ§μ„ μ§„ν–‰ν•˜λŠ” 것은 μ•„λ‹ˆλ©° μ λ‹Ήν•œ 후보가 보이면 λ¦¬νŒ©ν† λ§ 진행 (ex. μ½”λ“œ 쀑볡)

 

[λ¦¬νŽ™ν† λ§ μž₯점]

1. 지속적 λ¦¬νŒ©ν† λ§μ€ μ½”λ“œ 가독성이 높아짐

2. 가독성이 높아지면 κ°œλ°œμžλŠ” λΉ λ₯΄κ²Œ μ½”λ“œ 뢄석 κ°€λŠ₯

3. μˆ˜μ •μš”μ²­μ΄ μžˆλ‹€λ©΄ λ³€κ²½ μ½”λ“œλ₯Ό λΉ λ₯΄κ²Œ 찾을 수 있음 

 

πŸ“’ κ°€μž₯ μ€‘μš”: λ™μž‘ ν•˜λŠ” μ½”λ“œλ₯Ό λ§Œλ“œλŠ” 것
πŸ“’ SW 생쑴 기간이 κΈΈμ–΄μ§ˆμˆ˜λ‘ μ§€μ†μ μœΌλ‘œ μ½”λ“œ λ³€κ²½ ν•„μš”
πŸ“’ λ¦¬νŒ©ν† λ§: μ½”λ“œ 변경을 μœ„ν•΄ λ³€κ²½ν•˜κΈ° μ‰¬μš΄ ꡬ쑰둜 λ³€κ²½

- μž‘μ€ λ¦¬νŒ©ν† λ§μ€ λ°”λ‘œ λ³€κ²½ (λ³€μˆ˜ 이름 λ³€κ²½, μƒμˆ˜λ₯Ό λ³€μˆ˜λ‘œ λ³€κ²½)
- λ§€μ„œλ“œ μΆ”μΆœκ³Ό 같은 ꡬ쑰에 영ν–₯ μ£ΌλŠ” 것은 흐름이 λˆˆμ— λ“€μ–΄μ˜€κΈ° μ‹œμž‘ν•  λ•Œ!

πŸ’› ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± μˆœμ„œ μ—°μŠ΅

맀달 λΉ„μš©μ„ μ§€λΆˆν•΄μ•Ό μ‚¬μš©ν•  수 μžˆλŠ” 유료 μ„œλΉ„μŠ€κ°€ 있고 κ·œμΉ™μ€ λ‹€μŒκ³Ό κ°™λ‹€.

1. μ„œλΉ„μŠ€λ₯Ό μ‚¬μš©ν•˜λ €λ©° 맀달 1λ§Œμ›μ„ μ„ λΆˆλ‘œ λ‚©λΆ€, 납뢀일 κΈ°μ€€ ν•œλ‹¬ λ’€κ°€ μ„œλΉ„μŠ€ 만료일
2. 2κ°œμ›” 이상 μš”κΈˆμ„ λ‚©λΆ€ κ°€λŠ₯
3. 10λ§Œμ› λ‚©λΆ€ μ‹œ, μ„œλΉ„μŠ€ 1λ…„ 제곡

 

 

μ–΄λ–€ 것뢀터 ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜λŠ” 것이 μ’‹μ„κΉŒ? 즉, μ–΄λ–€ 것이 κ°€μž₯ μ‰¬μš΄ κ²ƒμΌκΉŒ?


🌱 μ‰¬μš΄ 것뢀터 ν…ŒμŠ€νŠΈ

ν…ŒμŠ€νŠΈλ₯Ό μΆ”κ°€ν•  λ•ŒλŠ” κ΅¬ν˜„ν•˜κΈ° μ‰½κ±°λ‚˜, μ˜ˆμ™Έ 사항을 λ¨Όμ € ν…ŒμŠ€νŠΈ ν•΄μ•Όν•œλ‹€λŠ” 점을 λ‹€μ‹œ μƒκΈ°μ‹œν‚€μž!

 

μœ„ μƒν™©μ—μ„œ κ°€μž₯ μ‰¬μš΄ 점은 1번일 것이닀. 

 

    @Test
    void λ§Œμ›_λ‚©λΆ€ν•˜λ©΄_ν•œλ‹¬_λ’€κ°€_만료일이_됨(){
        LocalDate billingDate = LocalDate.of(2019, 3, 1);
        int payAmount = 10_000;
        ExpiryDateCalculator cal = new ExpiryDateCalculator();
        LocalDate expiryDate = cal.calculateExpiryDate(billingDate, payAmount);
        assertEquals(expiryDate, LocalDate.of(2019, 4, 1));
    }
    ////
	public LocalDate calculateExpiryDate(LocalDate billingDate, int payAmount){
        return LocalDate.of(2019, 4, 1);
    }

🌱 예λ₯Ό μΆ”κ°€ν•˜λ©΄μ„œ κ΅¬ν˜„μ„ μΌλ°˜ν™”

μœ„μ˜ μ½”λ“œλŠ” ν•˜λ‚˜μ˜ λ‚ μ§œμ—λ§Œ λ§žμΆ°μ§„ μ½”λ“œμ΄κΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ μ˜ˆμ‹œμ˜ λ‚ μ§œλ₯Ό μ„ νƒν•˜μ—¬ 일반적인 λ‚ μ§œμ—λ„ testκ°€ 성곡할 수 μžˆλ„λ‘ μ½”λ“œλ‘œ 변경을 ν•˜μ˜€λ‹€.

    @Test
    void λ§Œμ›_λ‚©λΆ€ν•˜λ©΄_ν•œλ‹¬_λ’€κ°€_만료일이_됨(){
        LocalDate billingDate = LocalDate.of(2019, 3, 1);
        int payAmount = 10_000;
        ExpiryDateCalculator cal = new ExpiryDateCalculator();
        LocalDate expiryDate = cal.calculateExpiryDate(billingDate, payAmount);
        assertEquals(expiryDate, LocalDate.of(2019, 4, 1));

        LocalDate billingDate2 = LocalDate.of(2019, 5, 5);
        int payAmount2 = 10_000;
        ExpiryDateCalculator cal2 = new ExpiryDateCalculator();
        LocalDate expiryDate2 = cal2.calculateExpiryDate(billingDate2, payAmount2);
        assertEquals(expiryDate2, LocalDate.of(2019, 6, 5));
    }
    
    ///
	public LocalDate calculateExpiryDate(LocalDate billingDate, int payAmount){
        return billingDate.plusMonths(1);
    }

🌱 μ½”λ“œ 정리: 쀑볡 제거 & μ˜ˆμ™Έ 상황 처리

λ¦¬νŒ©ν† λ§μ„ μ§„ν–‰ν•˜μ—¬ μ€‘λ³΅λœ μ½”λ“œλ₯Ό μ œκ±°ν•œλ‹€.

보톡은 쀑볡을 μ œκ±°ν•˜λŠ” 것이 μ’‹μ§€λ§Œ, ν…ŒμŠ€νŠΈ μ½”λ“œμ˜ 쀑볡 μ œκ±°μ—μ„œλŠ” 고민이 ν•„μš”ν•˜λ‹€.

μ™œλ‚˜ν•˜λ©΄ 각 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλŠ” 무엇을 ν…ŒμŠ€νŠΈν•˜λŠ”μ§€ λͺ…ν™•ν•˜κ²Œ μ„€λͺ…이 μžˆμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. 

 

-> 쀑볡을 μ œκ±°ν•΄λ³΄κ³  ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ μ—¬μ „νžˆ μžμ‹ μ„ μ„€λͺ…ν•˜λŠ”μ§€ 확인 ν•„μš”

 

ν•œ 달 주기둜 λ‚ μ§œλ₯Ό μ„Έλ©΄ μ˜ˆμ™Έ 상황이 λ°œμƒν•œλ‹€

3μ›” 1일과 4μ›” 1일의 경우 λͺ¨λ‘ 1일에 λ‚©λΆ€ν•˜λ©΄ λ˜μ§€λ§Œ, 말일에 λ‚©λΆ€ν•˜λŠ” 경우

1μ›” 31일과 2μ›” 28일인 κ²½μš°μ΄λ‹€. 이 κ²½μš°λŠ” λͺ¨λ‘ LocalDate의 ν•¨μˆ˜κ°€ μ•Œμ•„μ„œ 계산해쀀닀고 ν•œλ‹€.

 

    @Test
    void 납뢀일과_ν•œλ‹¬_λ’€_μΌμžκ°€_같지_μ•ŠμŒ(){
        assertExpiryDate(LocalDate.of(2019, 1, 31),
                10_000, LocalDate.of(2019, 2, 28));
    }

    private void assertExpiryDate(LocalDate billingDate, int payAmount, LocalDate expectedExpiryDate) {
        ExpiryDateCalculator cal = new ExpiryDateCalculator();
        LocalDate expiryDate = cal.calculateExpiryDate(billingDate, payAmount);
        assertEquals(expiryDate, expectedExpiryDate);
    }
    
    ////
    
	public LocalDate calculateExpiryDate(LocalDate billingDate, int payAmount){
        return billingDate.plusMonths(1); // λ©”μ„œλ“œκ°€ μ•Œμ•„μ„œ λ‚ μ§œ μ£ΌκΈ°λ₯Ό μ²˜λ¦¬ν•΄μ€Œ
    }

 

🌱 λ‹€μŒ ν…ŒμŠ€νŠΈ 선택: λ‹€μ‹œ μ˜ˆμ™Έ 상황

μ—¬νƒœκΉŒμ§€λŠ” ν•œλ‹¬μ— λ§Œμ›λ§Œ λ‚©λΆ€ν•˜λŠ” 상황을 κ°€μ •ν•˜μ—¬ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν–ˆλ‹€. ν•˜μ§€λ§Œ, 두달 μ΄μƒμ˜ μš”κΈˆμ„ ν•œλ²ˆμ— λ‚©λΆ€ν•˜λŠ” κ²½μš°λ„ 있기 λ•Œλ¬Έμ— 두달을 λ‚©λΆ€ν•˜λŠ” 경우의 μ˜ˆμ™Έ 상황을 μ‚΄νŽ΄λ³΄μž. 

 

μ‰¬μš΄ 상황: 두달 μ΄μƒμ˜ μš”κΈˆμ„ ν•œ λ²ˆμ— λ‚©λΆ€ν•˜λŠ” 경우
μ˜ˆμ™Έ 상항: ν•œ 달을 λ‚©λΆ€ν•˜λŠ” 경우

ν˜„μž¬ ν•œ 달을 λ‚©λΆ€ν•˜λŠ” 경우의 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν–ˆκΈ° λ•Œλ¬Έμ— ν•œ 달을 λ‚©λΆ€ν•˜λŠ” 경우의 μ˜ˆμ™Έ 상황 μ²˜λ¦¬ν•˜κ³  μ§„ν–‰ν•˜λŠ” 것이 μ’‹λ‹€. 

μ΄λ•Œ, μ˜ˆμ™Έ 상황을 μ²˜λ¦¬ν•˜λ €λ©΄ 첫 납뢀일을 μ•Œμ•„μ•Ό ν•œλ‹€.

 

 

🌱 λ‹€μŒ ν…ŒμŠ€νŠΈλ₯Ό μΆ”κ°€ν•˜κΈ° 전에 λ¦¬νŒ©ν† λ§

λ§Œλ£ŒμΌμ„ κ³„μ‚°ν•˜λŠ” 데 ν•„μš”ν•œ 값이 λ‹€μŒκ³Ό 같이 λŠ˜μ–΄λ‚¬λ‹€.

calculateExpiryDate λ©”μ†Œλ“œ νŒŒλΌλ―Έν„°λ‘œ 첫 납뢀일 μΆ”κ°€
첫 납뢀일, 납뢀일, 납뢀앑을 담은 객체λ₯Ό calculateExpiryDate λ©”μ„œλ“œμ— 전달

 

ν˜„μž¬ νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬ν•΄μ•Όν•˜λŠ” 값은 μ„Έ 개! -> 첫 납뢀일, 납뢀일, λ‚©λΆ€μ•‘

이기 λ•Œλ¬Έμ— νŒŒλΌλ―Έν„°κ°€ 3개 이상이 되면 객체둜 μƒμ„±ν•΄μ„œ ν•œ λ²ˆμ— μ „λ‹¬ν•˜λŠ” 것이 μ’‹λ‹€ .

 

package testdriven.chap03;

import org.springframework.test.web.reactive.server.WebTestClient;

import java.time.LocalDate;

public class PayData {
    private LocalDate billingDate;
    private int payAmount;
    private PayData(){}

    public PayData(LocalDate billingDate, int payAmount){
        this.billingDate = billingDate;
        this.payAmount = payAmount;
    }

    public LocalDate getBillingDate(){
        return billingDate;
    }
    public int getPayAmount(){
        return payAmount;
    }

    public static Builder builder(){
        return new Builder();
    }

    public static class Builder{
        private PayData payData = new PayData();

        public Builder billingDate(LocalDate billingDate){
            payData.billingDate = billingDate;
            return this;
        }

        public Builder payAmount(int payAmount){
            payData.payAmount = payAmount;
            return this;
        }

        public PayData build(){
            return payData;
        }
    }
}

///
    public LocalDate calculateExpiryDate(PayData payData){
        return payData.getBillingDate().plusMonths(1);
    }
}

///
    @Test
    void λ§Œμ›_λ‚©λΆ€ν•˜λ©΄_ν•œλ‹¬_λ’€κ°€_만료일이_됨(){
        assertExpiryDate(PayData.builder()
                .billingDate(LocalDate.of(2019, 3, 1))
                .payAmount(10_000)
                .build(),
                LocalDate.of(2019, 4, 1));
        assertExpiryDate(PayData.builder()
                        .billingDate(LocalDate.of(2019, 1, 31))
                        .payAmount(10_000)
                        .build(),
                LocalDate.of(2019, 2, 28));
    }

 

κ°„λ‹¨ν•˜κ²Œ λΉŒλ” νŒ¨ν„΄μ„ μ μš©ν•˜λ©΄ μƒμˆ˜κ°€ λ‹€μŒ λœ»μ„ λ‚˜νƒ€λ‚Έλ‹€λŠ” 것을 μ‰½κ²Œ μ•Œ 수 μžˆλ‹€.

 

🌱 μ˜ˆμ™Έ 상황 ν…ŒμŠ€νŠΈ 진행 계속

μ—¬νƒœκΉŒμ§€λŠ” 첫 납뢀일을 κΈ°μ€€μœΌλ‘œ λ§Œλ£ŒμΌμ„ κ²Œμ‚°ν•˜λŠ” κ²ƒμ΄μ—ˆμ§€λ§Œ, μ΄μ œλŠ” λ‹€μŒκ³Ό 같은 상황도 κ³ λ €λ₯Ό 해보아야 ν•œλ‹€.

첫 납뢀일이 2019-01-31이고, λ§Œλ£Œλ˜λŠ” 2019-02-28에 1λ§Œμ›μ„ λ‚©λΆ€ν•˜λ©΄ λ‹€μŒ λ§Œλ£ŒμΌμ€ 2019-03-31이닀. 
 @Test
    void 첫_납뢀일과_만료일이_μΌμžκ°€_λ‹€λ₯Όλ•Œ_λ§Œμ›_λ‚©λΆ€(){
        PayData payData = PayData.builder()
                .firstBillingDate(LocalDate.of(2019, 5, 31))
                .billingDate(LocalDate.of(2019, 6, 30))
                .payAmount(10_000)
                .build();
        assertExpiryDate(payData, LocalDate.of(2019, 7, 31));
    }
    
    ///
public LocalDate calculateExpiryDate(PayData payData){
    int addedMonths = 1;
    if(payData.getFirstBillingDate() != null){
        LocalDate candidateExp = payData.getBillingDate().plusMonths(addedMonths);
        // 첫 λ‚©λΆ€ 일자(31일)와 후보 만료일의 일자(28일)이 같지 μ•Šμ„λ•Œ
        if(payData.getFirstBillingDate().getDayOfMonth() != candidateExp.getDayOfMonth()){
            // 첫 λ‚©λΆ€μΌμ˜ 일자λ₯Ό 후보 만료일의 일자둜 μ‚¬μš©
            return candidateExp.withDayOfMonth(payData.getFirstBillingDate().getDayOfMonth());
        }
    }
    return payData.getBillingDate().plusMonths(addedMonths);
}

 

🌱 λ‹€μŒ ν…ŒμŠ€νŠΈλ₯Ό 선택: μ‰¬μš΄ ν…ŒμŠ€νŠΈ

1. 2λ§Œμ›μ„ μ§€λΆˆν•˜λ©΄ 만료일이 두 달뒀가 λœλ‹€.
2. 3λ§Œμ›μ„ μ§€λΆˆν•˜λ©΄ 만료일이 석 달 λ’€κ°€ λœλ‹€.

λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œ ν…ŒμŠ€νŠΈκ°€ 성곡할 수 μžˆλ„λ‘ μ½”λ“œλ₯Ό λ³€κ²½ν•˜μž. 

@Test
void μ΄λ§Œμ›_이상_λ‚©λΆ€ν•˜λ©΄_λΉ„λ‘€ν•΄μ„œ_만료일_계산(){
    assertExpiryDate(PayData.builder()
            .billingDate(LocalDate.of(2019, 3, 1))
            .payAmount(20_000)
            .build(), LocalDate.of(2019, 5, 1));

    assertExpiryDate(PayData.builder()
            .billingDate(LocalDate.of(2019, 3, 1))
            .payAmount(30_000)
            .build(), LocalDate.of(2019, 6, 1));
}
///
public LocalDate calculateExpiryDate(PayData payData){
        int addedMonths = payData.getPayAmount()/10_000;
        //μƒλž΅
}

μ΄μ œλŠ” 첫 납뢀일과 λ‚©λΆ€μΌμ˜ μΌμžκ°€ λ‹€λ₯Ό λŒ€ 2λ§Œμ› 이상 λ‚©λΆ€ν•œ 경우의 μ˜ˆμ™Έ 상황도 κ³ λ €λ₯Ό 해보아야 ν•œλ‹€. 

 

public LocalDate calculateExpiryDate(PayData payData){
    int addedMonths = payData.getPayAmount()/10_000;
    if(payData.getFirstBillingDate() != null){
        LocalDate candidateExp = payData.getBillingDate().plusMonths(addedMonths);
        // 첫 λ‚©λΆ€ 일자(31일)와 후보 만료일의 일자(28일)이 같지 μ•Šμ„λ•Œ
        if(payData.getFirstBillingDate().getDayOfMonth() != candidateExp.getDayOfMonth()){
           // 후보 만료일(μ‹€μ œ λ‚ μ§œλ‘œ 확인)이 첫 λˆμ„ λ‚Έ μΌμžλ³΄λ‹€ 더 μž‘μ€ κ²½μš°μ—λŠ” 후보 만료일의 일자둜 return 
            if(YearMonth.from(candidateExp).lengthOfMonth()<payData.getFirstBillingDate().getDayOfMonth()){
                return candidateExp.withDayOfMonth(YearMonth.from(candidateExp).lengthOfMonth());
            }
            // 첫 λ‚©λΆ€μΌμ˜ 일자λ₯Ό 후보 만료일의 일자둜 μ‚¬μš©
            return candidateExp.withDayOfMonth(payData.getFirstBillingDate().getDayOfMonth());
        }
    }
    return payData.getBillingDate().plusMonths(addedMonths);
}

///
@Test
void 첫_납뢀일과_만료일_μΌμžκ°€_λ‹€λ₯Όλ•Œ_μ΄λ§Œμ›_이상_λ‚©λΆ€(){
    assertExpiryDate(PayData.builder()
            .firstBillingDate(LocalDate.of(2019, 1, 31))
            .billingDate(LocalDate.of(2019, 2, 28))
            .payAmount(20_000)
            .build(), LocalDate.of(2019, 4, 30));

    assertExpiryDate(PayData.builder()
            .firstBillingDate(LocalDate.of(2019, 3, 31))
            .billingDate(LocalDate.of(2019, 4, 30))
            .payAmount(30_000)
            .build(), LocalDate.of(2019, 7, 31));

}

 

🌱 μ½”λ“œ λ¦¬νŒ©ν† λ§

import java.time.LocalDate;
import java.time.YearMonth;

public class ExpiryDateCalculator {
    public LocalDate calculateExpiryDate(PayData payData) {
        int addedMonths = payData.getPayAmount() / 10_000;
        if (payData.getFirstBillingDate() != null) {
            return expiryDateUsingFirstBillingDate(payData, addedMonths);
        } else {
            return payData.getBillingDate().plusMonths(addedMonths);
        }
    }

    private LocalDate expiryDateUsingFirstBillingDate(PayData payData, int addedMonths) {
        LocalDate candidateExp = payData.getBillingDate().plusMonths(addedMonths);
        final int dayOfFirstBilling = payData.getFirstBillingDate().getDayOfMonth();
        // 첫 λ‚©λΆ€ 일자(31일)와 후보 만료일의 일자(28일)이 같지 μ•Šμ„λ•Œ
        if (isSameDayOfMonth(candidateExp, dayOfFirstBilling)) {
            // 후보 만료일(μ‹€μ œ λ‚ μ§œλ‘œ 확인)이 첫 λˆμ„ λ‚Έ μΌμžλ³΄λ‹€ 더 μž‘μ€ κ²½μš°μ—λŠ” 후보 만료일의 일자둜 return
            final int dayLenOfCandiMon = lastDayOfMonth(candidateExp);
            if (dayLenOfCandiMon < dayOfFirstBilling) {
                return candidateExp.withDayOfMonth(dayLenOfCandiMon);
            }
            // 첫 λ‚©λΆ€μΌμ˜ 일자λ₯Ό 후보 만료일의 일자둜 μ‚¬μš©
            return candidateExp.withDayOfMonth(dayOfFirstBilling);
        }else{
            return candidateExp;
        }
    }

    private boolean isSameDayOfMonth(LocalDate candidateExp, int dayOfFirstBilling) {
        return dayOfFirstBilling != candidateExp.getDayOfMonth();
    }
    private int lastDayOfMonth(LocalDate date) {
        return YearMonth.from(date).lengthOfMonth();
    }
}

 

🌱 10κ°œμ›” μš”κΈˆμ„ λ‚©λΆ€ν•˜λ©΄ 1κ°œμ›” 제곡

이제 10κ°œμ›”μ„ λ‚©λΆ€ν•˜λ©΄ μ„œλΉ„μŠ€λ₯Ό 1λ…„ μ œκ³΅ν•΄μ£ΌλŠ” ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•΄λ³΄μž.

public LocalDate calculateExpiryDate(PayData payData) {
    int addedMonths = payData.getPayAmount() == 100_000 ? 12: payData.getPayAmount() / 10_000;
    if (payData.getFirstBillingDate() != null) {
        return expiryDateUsingFirstBillingDate(payData, addedMonths);
    } else {
        return payData.getBillingDate().plusMonths(addedMonths);
    }
}

 

🌱 ν…ŒμŠ€νŠΈ μ‹œ 주의 사항

1. μ²˜μŒλΆ€ν„° λͺ¨λ“  ν…ŒμŠ€νŠΈ 사둀λ₯Ό μƒκ°ν•˜λ©΄ μ‹œκ°„λ„ 였래 걸릴 λΏλ”λŸ¬ 쉽지도 μ•ŠμŒ

2. μƒˆλ‘œ λ°œκ²¬ν•œ ν…ŒμŠ€νŠΈ 사둀λ₯Ό μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈλ‘œ λ“±λ‘ν•˜λŠ” 것도 사둀λ₯Ό λ†“μΉ˜μ§€ μ•ŠλŠ” 방법

3. 'μ§€λΌλ‚˜ 트렐둜' μ‹œμŠ€ν…œμ„ μ‚¬μš©ν•˜λ©΄ ν…ŒμŠ€νŠΈ 사둀λ₯Ό ν•˜μœ„ μž‘μ—…μœΌλ‘œ 등둝 κ°€λŠ₯ (톡과 μ—¬λΆ€ 좔적 κ°€λŠ₯)

4. ν…ŒμŠ€νŠΈ λͺ©λ‘μ„ ν•˜λ‚˜μ˜ ν…ŒμŠ€νŠΈλ‘œ λ§Œλ“€λ©΄ μ•ˆλ¨

 

728x90