🌱TDD: λŒ€μ—­ & ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ 섀계

HyojinΒ·2024λ…„ 8μ›” 30일
0

TDD

λͺ©λ‘ 보기
3/4

CHAPTER 7: λŒ€μ—­

ν…ŒμŠ€νŠΈ μž‘μ„±ν•˜λ‹€ 보면 μ™ΈλΆ€ μš”μΈμ΄ ν•„μš”ν•œ μ‹œμ μ΄ μžˆλ‹€.

  • ν…ŒμŠ€νŠΈ λŒ€μƒμ—μ„œ 파일 μ‹œμŠ€ν…œμ„ μ‚¬μš©
  • ν…ŒμŠ€νŠΈ λŒ€μƒμ—μ„œ DBλ‘œλΆ€ν„° 데이터λ₯Ό μ‘°νšŒν•˜κ±°λ‚˜ 데이터λ₯Ό μΆ”κ°€
  • ν…ŒμŠ€νŠΈ λŒ€μƒμ—μ„œ μ™ΈλΆ€μ˜ HTTP μ„œλ²„μ™€ 톡신

μžλ™ 이체 κΈ°λŠ₯

public class AutoDebitRegisterTest {
    private AutoDebitRegister register;

    @BeforeEach
    void setUp() {
        CardNumberValidator validator = new CardNumberValidator();
        AutoDebitInfoRepository repository = new JpaAutoDebitInfoRepository();
        register = new AutoDebitRegister(validator, repository);
    }

    @Test
    void validCard() {
        // μ—…μ²΄μ—μ„œ 받은 ν…ŒμŠ€νŠΈμš© μœ νš¨ν•œ μΉ΄λ“œ 번호 μ‚¬μš©
        AutoDebitReq req = new AutoDebitReq("user1", "1234123412341234");
        RegisterResult result = this.register.register(req);
        assertEquals(VALID, result.getValidity());
    }

    @Test
    void theftCard() {
        // μ—…μ²΄μ—μ„œ 받은 λ„λ‚œ ν…ŒμŠ€νŠΈμš© μΉ΄λ“œ 번호 μ‚¬μš©
        AutoDebitReq req = new AutoDebitReq("user1", "1234567890123456");
        RegisterResult result = this.register.register(req);
        assertEquals(THEFT, result.getValidity());
    }
}

문제점

  • μΉ΄λ“œ λ²ˆν˜Έκ°€ ν•œ 달 뒀에 만료되면 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ‚¬μš©ν•  수 μ—†μŒ
  • μ™ΈλΆ€ 업체가 ν…ŒμŠ€νŠΈ μš©λ„μ˜ λ„λ‚œ μΉ΄λ“œ 정보λ₯Ό μ‚­μ œν•  경우

μ΄λ ‡κ²Œ ν…ŒμŠ€νŠΈ λŒ€μƒμ—μ„œ μ˜μ‘΄ν•˜λŠ” μš”μΈ λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈκ°€ μ–΄λ €μšΈ λ•ŒλŠ” λŒ€μ—­μ„ μ¨μ„œ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•  수 μžˆλ‹€.

λŒ€μ—­μ„ μ΄μš©ν•œ ν…ŒμŠ€νŠΈ

CardNumberValidator λŒ€μ—­ 클래슀

public class StubCardNumberValidator extends CardNumberValidator {
    private String invalidNo;
    private String theftNo;

    public void setInvalidNo(String invalidNo) {
        this.invalidNo = invalidNo;
    }

    public void setTheftNo(String theftNo) {
        this.theftNo = theftNo;
    }

    @Override
    public CardValidity validate(String cardNumber) {
        if (invalidNo != null && invalidNo.equals(cardNumber)) {
            return CardValidity.INVALID;
        }
        if (theftNo != null && theftNo.equals(cardNumber)) {
            return CardValidity.THEFT;
        }
        return CardValidity.VALID;
    }
}

이처럼 λ‹¨μˆœν•œ κ΅¬ν˜„μœΌλ‘œ μ‹€μ œ κ΅¬ν˜„μ„ λŒ€μ²΄ν•œλ‹€.

λŒ€μ—­μ„ μ΄μš©ν•΄μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±

public class AutoDebitRegister_Stub_Test {
    private AutoDebitRegister register;
    private StubCardNumberValidator stubValidator;
    private StubAutoDebitInfoRepository stubRepository;

    @BeforeEach
    void setUp() {
        stubValidator = new StubCardNumberValidator();
        stubRepository = new StubAutoDebitInfoRepository();
        register = new AutoDebitRegister(stubValidator, stubRepository);
    }

    @Test
    void invalidCard() {
        stubValidator.setInvalidNo("111122223333");

        AutoDebitReq req = new AutoDebitReq("user1", "111122223333");
        RegisterResult result = this.register.register(req);

        assertEquals(INVALID, result.getValidity());
    }

    @Test
    void theftCard() {
        stubValidator.setTheftNo("1234567890123456");

        AutoDebitReq req = new AutoDebitReq("user1", "1234567890123456");
        RegisterResult result = this.register.register(req);

        assertEquals(CardValidity.THEFT, result.getValidity());
    }

    @Test
    void validCard() {
        AutoDebitReq req = new AutoDebitReq("user1", "1234123412341234");
        RegisterResult result = this.register.register(req);
        assertEquals(VALID, result.getValidity());
    }
}
  • AutoDebitRegister κ°μ²΄λŠ” μ‹€μ œ 객체 λŒ€μ‹  StubCardNumberValidator을 μ‚¬μš©ν•΄μ„œ μΉ΄λ“œλ²ˆν˜Έκ°€ μœ νš¨ν•œμ§€ κ²€μ‚¬ν•˜κ³ , λ„λ‚œ μΉ΄λ“œλ²ˆν˜Έμ— λŒ€ν•œ μžλ™μ΄μ²΄ κΈ°λŠ₯ ν…ŒμŠ€νŠΈλ₯Ό κ²€μ‚¬ν•œλ‹€

DB 연동 μ½”λ“œ λŒ€μ—­

λŒ€μ—­μ„ μ‚¬μš©ν•˜λ©΄ DB 없이 AutoDebitRegisterλ₯Ό ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€.

AutoDebitInfoRepository λŒ€μ—­ κ΅¬ν˜„

public class MemoryAutoDebitInfoRepository implements AutoDebitInfoRepository {
    private Map<String, AutoDebitInfo> infos = new HashMap<>();

    @Override
    public void save(AutoDebitInfo info) {
        infos.put(info.getUserId(), info);
    }

    @Override
    public AutoDebitInfo findOne(String userId) {
        return infos.get(userId);
    }
}

MemoryAutoDebitInfoRepository λ₯Ό μ΄μš©ν•œ ν…ŒμŠ€νŠΈ

public class AutoDebitRegister_Fake_Test {
    private AutoDebitRegister register;
    private StubCardNumberValidator cardNumberValidator;
    private MemoryAutoDebitInfoRepository repository;

    @BeforeEach
    void setUp() {
        cardNumberValidator = new StubCardNumberValidator();
        repository = new MemoryAutoDebitInfoRepository();
        register = new AutoDebitRegister(cardNumberValidator, repository);
    }

    @Test
    void alreadyRegistered_InfoUpdated() {
        repository.save(
                new AutoDebitInfo("user1", "111222333444", LocalDateTime.now()));

        AutoDebitReq req = new AutoDebitReq("user1", "123456789012");
        RegisterResult result = this.register.register(req);

        AutoDebitInfo saved = repository.findOne("user1");
        assertEquals("123456789012", saved.getCardNumber());
    }

    @Test
    void notYetRegistered_newInfoRegistered() {
        AutoDebitReq req = new AutoDebitReq("user1", "1234123412341234");
        RegisterResult result = this.register.register(req);

        AutoDebitInfo saved = repository.findOne("user1");
        assertEquals("1234123412341234", saved.getCardNumber());
    }
}

λŒ€μ—­μ„ μ‚¬μš©ν•œ μ™ΈλΆ€ 상황 흉내와 κ²°κ³Ό 검증

  • λŒ€μ—­μ„ μ΄μš©ν•΄μ„œ μ™ΈλΆ€μ˜ 상황을 흉내냄
  • λŒ€μ—­μ„ μ΄μš©ν•˜λ©΄ 외뢀에 λŒ€ν•œ κ²°κ³Όλ₯Ό 검증할 수 있음

λŒ€μ—­μ˜ μ’…λ₯˜

λŒ€μ—­ μ’…λ₯˜μ„€λͺ…
Stubκ΅¬ν˜„μ„ λ‹¨μˆœν•œ κ²ƒμœΌλ‘œ λŒ€μ²΄ν•œλ‹€.
ν…ŒμŠ€νŠΈμ— 맞게 λ‹¨μˆœνžˆ μ›ν•˜λŠ” λ™μž‘μ„ μˆ˜ν–‰ν•œλ‹€.
예) StubCardNumberValidator
Fakeμ œν’ˆμ—λŠ” μ ν•©ν•˜μ§€ μ•Šμ§€λ§Œ, μ‹€μ œ λ™μž‘ν•˜λŠ” κ΅¬ν˜„μ„ μ œκ³΅ν•œλ‹€.
DB λŒ€μ‹ μ— λ©”λͺ¨λ¦¬λ₯Ό μ΄μš©ν•΄μ„œ κ΅¬ν˜„ν•œ MemoryAutoDebitInfoRepositoryκ°€ κ°€μ§œ λŒ€μ—­μ— ν•΄λ‹Ήν•œλ‹€.
Spy호좜된 내역을 κΈ°λ‘ν•œλ‹€.
κΈ°λ‘ν•œ λ‚΄μš©μ€ ν…ŒμŠ€νŠΈ κ²°κ³Όλ₯Ό 검증할 λ•Œ μ‚¬μš©ν•œλ‹€.
μŠ€ν…μ΄κΈ°λ„ ν•˜λ‹€.
MockκΈ°λŒ€ν•œ λŒ€λ‘œ μƒν˜Έμž‘μš©ν•˜λŠ”μ§€ ν–‰μœ„λ₯Ό κ²€μ¦ν•œλ‹€.
κΈ°λŒ€ν•œ λŒ€λ‘œ λ™μž‘ν•˜μ§€ μ•ŠμœΌλ©΄ μ΅μ…‰μ…˜μ„ λ°œμƒν•  수 μžˆλ‹€.
λͺ¨μ˜ κ°μ²΄λŠ” μŠ€ν…μ΄μž μŠ€νŒŒμ΄λ„ λœλ‹€.

λŒ€μ—­ μ’…λ₯˜λ³„ νšŒμ›κ°€μž… κΈ°λŠ₯ ν…ŒμŠ€νŠΈ

각 νƒ€μž…μ˜ μ—­ν• 

  • UserRegister : νšŒμ› κ°€μž…μ— λŒ€ν•œ 핡심 λ‘œμ§μ„ μˆ˜ν–‰ν•œλ‹€.
  • WeakPasswordChecker : μ•”ν˜Έκ°€ μ•½ν•œμ§€ κ²€μ‚¬ν•œλ‹€.
  • UserRepository : νšŒμ› 정보λ₯Ό μ €μž₯ν•˜κ³  μ‘°νšŒν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€.
  • EmailNotifier : 이메일 λ°œμ†‘ κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€.

κ΅¬ν˜„ν•˜κΈ° 전에 섀계

λ‹¨μœ„ κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ°μ— μ•žμ„œ μ–΄λ–€ ꡬ성 μš”μ†Œκ°€ ν•„μš”ν• μ§€ κ³ λ―Όν•˜λŠ” 것은 의쑴 λŒ€μƒμ„ λ„μΆœν•  λ•Œ 도움이 λœλ‹€.

Stub: μ•½ν•œ μ•”ν˜Έ 확인 κΈ°λŠ₯

μ•½ν•œ μ•”ν˜Έ 인지 μ—¬λΆ€λ₯Ό μ•Œλ €μ£ΌκΈ°λ§Œ ν•˜λ©΄ λ˜λ―€λ‘œ μŠ€ν… λŒ€μ—­μ„ μ‚¬μš©ν•œλ‹€.

Faker: 리포지토리

ν…ŒμŠ€νŠΈ 골격

  • 동일 IDλ₯Ό κ°€μ§„ νšŒμ› 쑴재
  • μ‹€ν–‰ 및 κ²°κ³Ό 검증

방법

  • 리포지토리에 μ‚¬μš©μž μΆ”κ°€

Spy: 이메일 λ°œμ†‘ μ—¬λΆ€ 확인

κ²€μ¦ν•˜κΈ° μœ„ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œ 골격

  • μ‹€ν–‰: userRegister.register(id, pw, email)
  • κ²°κ³Ό: email@somedomain.com으둜 이메일 λ°œμ†‘μ„ μš”μ²­ν–ˆλŠ”μ§€ 확인

이메일 λ°œμ†‘ μ—¬λΆ€λ₯Ό μ–΄λ–»κ²Œ 확인 방법

  • UserRegisterκ°€ EmailNotifier의 메일 λ°œμ†‘ κΈ°λŠ₯을 μ‹€ν–‰ν•  λ•Œ νŠΉμ • 이메일을 μ‚¬μš©ν–ˆλŠ”μ§€ 확인

슀파이 κ΅¬ν˜„

package com.example.tdd.chap07.user;

public class SpyEmailNotifier implements EmailNotifier {
    private boolean called;
    private String email;

    public boolean isCalled() {
        return called;
    }

    public String getEmail() {
        return email;
    }
}

이 단언을 ν†΅κ³Όν•˜λ €λ©΄ λ‹€μŒ 두가지λ₯Ό ν•΄μ•Όν•œλ‹€

  • UserRegisterκ°€ EmailNotifier의 이메일 λ°œμ†‘ κΈ°λŠ₯을 호좜
  • 슀파이의 이메일 λ°œμ†‘ κΈ°λŠ₯ κ΅¬ν˜„μ—μ„œ 호좜 μ—¬λΆ€ 기둝
    // UserRegister
    public void register(String id, String pw, String email) {
        if (passwordChecker.checkPasswordWeak(pw)) {
            throw new WeakPasswordException();
        }
        User user = userRepository.findById(id);
        if (user != null) {
            throw new DupIdException();
        }
        userRepository.save(new User(id, pw, email));

        emailNotifier.sendRegisterEmail(email);
    }
    // SpyEmailNotifier				
    @Override
    public void sendRegisterEmail(String email) {
        this.called = true;
        this.email = email;
    }

Mockito: λͺ¨μ˜ 객체둜 μŠ€ν…κ³Ό 슀파이 λŒ€μ²΄

Mockito 기초 μ‚¬μš©λ²•

의쑴 μ„€μ •

dependencies {
		// mockito 의쑴 μ„€μ •
    testImplementation('org.mockito:mockito-core:2.26.0')
}

λͺ¨μ˜ 객체 생성

Mockito.mock() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜λ©΄ νŠΉμ • νƒ€μž…μ˜ λͺ¨μ˜ 객체λ₯Ό 생성할 수 μžˆλ‹€.

Mockito.mock() λ©”μ„œλ“œλŠ” 클래슀, μΈν„°νŽ˜μ΄μŠ€, 좔상 ν΄λž˜μŠ€μ— λŒ€ν•΄ λͺ¨μ˜ 객체λ₯Ό 생성할 수 μžˆλ‹€.

    @Test
    void mockStubTest() {
		    // GameNumGen νƒ€μž…μ˜ λͺ¨μ˜ 객체 생성
        GameNumGen genMock = **mock**(GameNumGen.class);
    }
public interface GameNumGen {
    String generate(GameLevel level);
}

μŠ€ν… μ„€μ •

λͺ¨μ˜ 객체λ₯Ό μƒμ„±ν•œ λ’€μ—λŠ” BDDMockito 클래슀λ₯Ό μ΄μš©ν•΄μ„œ λͺ¨μ˜ 객체에 μŠ€ν…μ„ ꡬ성할 수 μžˆλ‹€.

BDDMockito.given

    import static org.mockito.BDDMockito.given;
    
    @Test
    void mockStubTest() {
		    // Mock 생성
        GameNumGen genMock = mock(GameNumGen.class);
        // Stub ꡬ성
        **given(genMock.generate(GameLevel.EASY)).willReturn("123");**
				// Stub 섀정에 λ§€μΉ­λ˜λŠ” λ©”μ„œλ“œ μ‹€ν–‰
        String num = **genMock.generate(GameLevel.EASY)**;
        assertEquals("123", num);
    }
    
    @Test
    void mockThrowTest() {
        GameNumGen genMock = mock(GameNumGen.class);
        **given(genMock.generate(null)).willThrow(new IllegalArgumentException());**

        assertThrows(
                IllegalArgumentException.class,
                () -> **genMock.generate(null)**);
    }

BDDMockito.given을 μ΄μš©ν•˜λ©΄ λͺ¨μ˜ 객체의 λ©”μ„œλ“œκ°€ νŠΉμ • 값을 λ¦¬ν„΄ν•˜λ„λ‘ μ„€μ •ν•  수 μžˆλ‹€.

  • willReturn() λ©”μ„œλ“œλŠ” μŠ€ν…μ„ μ •μ˜ν•œ λ©”μ„œλ“œκ°€ 리턴할 값을 μ§€μ •ν•œλ‹€.
  • willThrow() λ©”μ„œλ“œλŠ” μ΅μ…‰μ…˜μ„ λ°œμƒν•˜κ²Œ μ„€μ •ν•  수 μžˆλ‹€.

리턴 νƒ€μž…μ΄ void인 λ©”μ„œλ“œμ— λŒ€ν•΄ μ΅μ…‰μ…˜μ„ λ°œμƒμ‹œν‚€λŠ” 방법

public class VoidMethodStubTest {
    @Test
    void voidMethodWillThrowTest() {
        List<String> mockList = mock(List.class);
        **willThrow**(UnsupportedOperationException.class)
                .**given**(mockList)
                .clear();

        assertThrows(
                UnsupportedOperationException.class,
                () -> mockList.clear()
        );
    }
}
  • given() λ©”μ„œλ“œλŠ” 인자둜 전달받은 λͺ¨μ˜ 객체 μžμ‹ μ„ λ¦¬ν„΄ν•˜λŠ”λ° μ΄λ•Œ μ΅μ…‰μ…˜μ„ λ°œμƒν•  λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.
  • 읡센μ…₯을 λ°œμƒν•  λͺ¨μ˜ 객체 μ„€μ •

인자 맀칭 처리

MockitoλŠ” μΌμΉ˜ν•˜λŠ” μŠ€ν… 섀정이 없을 경우 리턴 νƒ€μž…μ˜ κΈ°λ³Έ 값을 λ¦¬ν„΄ν•œλ‹€.
예λ₯Ό λ“€μ–΄ 리턴 νƒ€μž…μ΄ intλ©΄ 0을 λ¦¬ν„΄ν•˜κ³ , boolean이면 falseλ₯Ό λ¦¬ν„΄ν•œλ‹€. κΈ°λ³Έ 데이터 νƒ€μž…μ΄ μ•„λ‹Œ Stringμ΄λ‚˜ List와 같은 μ°Έμ‘° νƒ€μž…μ΄λ©΄ null을 λ¦¬ν„΄ν•œλ‹€.

  • ArgumentMatchers.any() λ©”μ„œλ“œλŠ” λͺ¨λ“  값에 μΌμΉ˜ν•˜λ„λ‘ μŠ€ν…μ„ μ„€μ •ν•œλ‹€.
    • anyνƒ€μž…(), anyString(), any(), anyμ»¬λ ‰μ…˜νƒ€μž…(), matches(String || Pattern), eq(κ°’)

μŠ€ν…μ„ μ„€μ •ν•  λ©”μ„œλ“œμ˜ μΈμžκ°€ 두 개 이상인 경우 주의점
MockitoλŠ” ν•œ μΈμžλΌλ„ ArgumentMatcherλ₯Ό μ‚¬μš©ν•  경우 λͺ¨λ“  인자λ₯Ό ArgumentMatcherλ₯Ό μ΄μš©ν•΄μ„œ μ„€μ •ν•˜λ„λ‘ ν•˜κ³  μžˆλ‹€.

ν–‰μœ„ 검증

λͺ¨μ˜ 객체의 ν–‰μœ„λ₯Ό κ²€μ¦ν•œλ‹€.

λͺ¨μ˜ 객체 genMock의 generate() λ©”μ†Œλ“œκ°€ GameLevel.EASY 인자λ₯Ό μ‚¬μš©ν•΄μ„œ ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€ κ²€μ¦ν•œλ‹€.

public class GameTest {
    @Test
    void init() {
        GameNumGen genMock = mock(GameNumGen.class);
        Game game = new Game(genMock);
        game.init(GameLevel.EASY);

				**then**(genMock).**should**().generate(GameLevel.EASY);
        // **then**(genMock).**should**(only()).generate(GameLevel.EASY);
    }
}

BDDMockito.then()은 λ©”μ„œλ“œ 호좜 μ—¬λΆ€λ₯Ό 검증할 λͺ¨μ˜ 객체λ₯Ό μ „λ‹¬λ°›λŠ”λ‹€. should() λ©”μ„œλ“œλŠ” λͺ¨μ˜ 객체의 λ©”μ„œλ“œκ°€ λΆˆλ €μ•Ό ν•œλ‹€λŠ” 것을 μ„€μ •ν•˜κ³  μ‹€μ œ λΆˆλ €μ•Ό ν•  λ©”μ„œλ“œλ₯Ό μ§€μ •ν•œλ‹€.

λ©”μ„œλ“œ 호좜 횟수λ₯Ό 검증할 수 μžˆλ‹€.

  • only(), times(int), never(), atLeast(int), atLeastOne(), atMost(int)

인자 캑처

λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜λ‹€λ³΄λ©΄ λͺ¨μ˜ 객체λ₯Ό ν˜ΈμΆœν•  λ•Œ μ‚¬μš©ν•œ 인자λ₯Ό 검증해야 ν•  λ•Œκ°€ μžˆλ‹€.
Mockito의 ArgumentCaptorλ₯Ό μ‚¬μš©ν•˜λ©΄ λ©”μ„œλ“œ 호좜 μ—¬λΆ€λ₯Ό κ²€μ¦ν•˜λŠ” κ³Όμ •μ—μ„œ μ‹€μ œ ν˜ΈμΆœν•  λ•Œ μ „λ‹¬ν•œ 인자λ₯Ό 보관할 수 μžˆλ‹€.

    @DisplayName("κ°€μž…ν•˜λ©΄ 메일을 전솑함")
    @Test
    void whenRegisterThenSendMail() {
        userRegister.register("id", "pw", "email@email.com");

        **ArgumentCaptor**<String> captor = **ArgumentCaptor.forClass(String.class);**
        BDDMockito.then(mockEmailNotifier).should()
	        .sendRegisterEmail(**captor.capture()**);

        String realEmail = **captor.getValue()**;
        assertEquals("email@email.com", realEmail);
    }
  • `ArgumentCaptor captor = ArgumentCaptor.forClass(String.class);` : String νƒ€μž…μ˜ 인자λ₯Ό 보관할 μˆ˜μžˆλŠ” ArgumentCaptorλ₯Ό μƒμ„±ν•œλ‹€.
  • BDDMockito.then(mockEmailNotifier).should().sendRegisterEmail(**captor.capture()**); : λͺ¨μ˜ 객체 호좜 μ—¬λΆ€λ₯Ό κ²€μ¦ν•˜λŠ” μ½”λ“œμ—μ„œ 인자둜 μ „λ‹¬ν•œλ‹€. 인자둜 전달할 λ•ŒλŠ” ArgumentCaptor#captor() λ©”μ„œλ“œλ₯Ό μ „λ‹¬ν•œλ‹€.

JUnit5 ν™•μž₯ μ„€μ •

mockito-junit-jupiter μ˜μ‘΄μ„ μΆ”κ°€ν•˜λ©΄ μ• λ…Έν…Œμ΄μ…˜μ„ μ΄μš©ν•΄μ„œ λͺ¨μ˜ 객체λ₯Ό 생성할 수 μžˆλ‹€.
@Mock μ• λ…Έν…Œμ΄μ…˜μ„ 뢙인 ν•„λ“œμ— λŒ€ν•΄ μžλ™μœΌλ‘œ λͺ¨μ˜ 객체λ₯Ό 생성해쀀닀.

@ExtendWith(MockitoExtension.class)
public class JUnit5ExtensionTest {
	@Mock
	private GameNumGen genMock;
}

상황과 κ²°κ³Ό 확인을 μœ„ν•œ ν˜‘μ—… λŒ€μƒ(의쑴) λ„μΆœκ³Ό λŒ€μ—­ μ‚¬μš©

μ œμ–΄ν•˜κΈ° νž˜λ“  μ™ΈλΆ€ 상황이 μ‘΄μž¬ν•˜λ©΄ λŒ€μ—­μœΌλ‘œ λŒ€μ‹ ν•  수 μžˆλ‹€

  • μ œμ–΄ν•˜κΈ° νž˜λ“  μ™ΈλΆ€ 상황을 별도 νƒ€μž…μœΌλ‘œ 뢄리
  • ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” λ³„λ„λ‘œ λΆ„λ¦¬ν•œ νƒ€μž…μ˜ λŒ€μ—­μ„ 생성
  • μƒμ„±ν•œ λŒ€μ—­μ„ ν…ŒμŠ€νŠΈ λŒ€μƒμ˜ μƒμ„±μž 등을 μ΄μš©ν•΄μ„œ 전달
  • λŒ€μ—­μ„ μ΄μš©ν•΄μ„œ 상황 ꡬ성

λ‹Ήμž₯ κ΅¬ν˜„ν•˜λŠ”λ° μ‹œκ°„μ΄ κ±Έλ¦¬λŠ” λ‘œμ§λ„ λΆ„λ¦¬ν•˜κΈ°μ— 쒋은 후보이닀.

λŒ€μ—­κ³Ό 개발 속도

λŒ€μ—­μ„ μ‚¬μš©ν•˜μ§€ μ•Šκ³  μ‹€μ œ κ΅¬ν˜„μ„ μ‚¬μš©ν•œλ‹€λ©΄ λŒ€κΈ° μ‹œκ°„μ΄ λ°œμƒν•œλ‹€.

  • μΉ΄λ“œ 정보 제곡 μ—…μ²΄μ—μ„œ λ„λ‚œ μΉ΄λ“œλ²ˆν˜Έλ₯Ό 받을 λ•ŒκΉŒμ§€ ν…ŒμŠ€νŠΈλ₯Ό κΈ°λ‹€λ¦°λ‹€.
  • μΉ΄λ“œ 정보 제곡 APIκ°€ 비정상 응닡을 μ£ΌλŠ” 상황을 ν…ŒμŠ€νŠΈν•˜κΈ° μœ„ν•΄ μ—…μ²΄μ˜ λ³€κ²½ λŒ€μ‘μ„ κΈ°λ‹€λ¦°λ‹€.
  • νšŒμ› κ°€μž… ν…ŒμŠ€νŠΈλ₯Ό ν•œ 뒀에 νŽΈμ§€κ°€ 도착할 λ•ŒκΉŒμ§€ 메일함을 ν™•μΈν•œλ‹€.
  • μ•½ν•œ μ•”ν˜Έ 검사 κΈ°λŠ₯을 κ°œλ°œν•  λ•ŒκΉŒμ§€ νšŒμ› κ°€μž… ν…ŒμŠ€νŠΈλ₯Ό λŒ€κΈ°ν•œλ‹€.

λŒ€μ—­μ„ μ‚¬μš©ν•˜λ©΄ μž₯점

  • μ‹€μ œ κ΅¬ν˜„μ΄ 없어도 λ‹€μ–‘ν•œ 상황에 λŒ€ν•΄ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€.
  • μ‹€μ œ κ΅¬ν˜„μ΄ 없어도 μ‹€ν–‰ κ²°κ³Όλ₯Ό 확인할 수 μžˆλ‹€.

λͺ¨μ˜ 객체λ₯Ό κ³Όν•˜κΈ° μ‚¬μš©ν•˜μ§€ μ•ŠκΈ°

문제

save() λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€ 확인해야 ν•˜κ³  ArgumentCaptorλ₯Ό μ΄μš©ν•΄μ„œ ν˜ΈμΆœν•  λ•Œ μ „λ‹¬ν•œ 인자λ₯Ό μ €μž₯ν•΄μ•Ό ν•œλ‹€. β†’ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ λ³΅μž‘ν•΄μ§

ν•΄κ²°

λ©”λͺ¨λ¦¬λ₯Ό μ΄μš©ν•œ κ°€μ§œ κ΅¬ν˜„μ„ μ‚¬μš©ν•˜λ©΄ μ½”λ“œκ°€ λ‹¨μˆœν•΄μ§€κ³  μ˜λ―Έλ„ λͺ…ν™•ν•˜λ‹€.

λͺ¨μ˜ 객체λ₯Ό μ΄μš©ν•˜λ©΄ λŒ€μ—­ 클래슀λ₯Ό λ§Œλ“€μ§€ μ•Šμ•„λ„ λ˜λ‹ˆκΉŒ νŽΈν•  수 μžˆλ‹€.

ν•˜μ§€λ§Œ κ²°κ³Ό 값을 ν™•μΈν•˜λŠ” μˆ˜λ‹¨μœΌλ‘œ λͺ¨μ˜ 객체λ₯Ό μ‚¬μš©ν•˜κΈ° μ‹œμž‘ν•˜λ©΄ κ²°κ³Ό 검증 μ½”λ“œκ°€ κΈΈμ–΄μ§€κ³  λ³΅μž‘ν•΄μ§„λ‹€.


Chapter 08: ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ 섀계

ν…ŒμŠ€νŠΈκ°€ μ–΄λ €μš΄ μ½”λ“œν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ 섀계좔가 μ„€λͺ…
ν•˜λ“œ μ½”λ”©λœ κ²½λ‘œκ²½λ‘œλ‚˜ μƒμˆ˜λ₯Ό μƒμ„±μžλ‚˜ λ©”μ„œλ“œ νŒŒλΌλ―Έν„°λ‘œ λ°›κΈ°ν•˜λ“œ μ½”λ”©λœ 값을 μ œκ±°ν•˜κ³ , μœ μ—°ν•˜κ²Œ λ³€κ²½ν•  수 μžˆλ„λ‘ μ„€μ •ν•˜κ±°λ‚˜ μ£Όμž…ν•¨μœΌλ‘œμ¨ λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μ μš©ν•  수 있음.
ν•˜λ“œ μ½”λ”©λœ μƒμˆ˜μƒμˆ˜λ₯Ό μƒμ„±μžλ‚˜ λ©”μ„œλ“œ νŒŒλΌλ―Έν„°λ‘œ λ°›κΈ°μƒμˆ˜λ₯Ό μ™ΈλΆ€μ—μ„œ μ£Όμž…λ°›μŒμœΌλ‘œμ¨ λ‹€μ–‘ν•œ ν™˜κ²½μ—μ„œμ˜ λ™μž‘μ„ ν…ŒμŠ€νŠΈν•  수 있으며, μ½”λ“œμ˜ μž¬μ‚¬μš©μ„±λ„ 높아짐.
의쑴 객체λ₯Ό 직접 μƒμ„±μ˜μ‘΄ λŒ€μƒμ„ μ£Όμž…λ°›κΈ° (μƒμ„±μž λ˜λŠ” μ„Έν„°λ₯Ό 톡해)직접 객체λ₯Ό μƒμ„±ν•˜λ©΄ ν…ŒμŠ€νŠΈ 쀑에 λͺ¨ν‚Ήμ΄ 어렀움. μ£Όμž…λ°›λ„λ‘ μ„€κ³„ν•˜λ©΄ μ˜μ‘΄μ„±μ„ μ‰½κ²Œ κ΅μ²΄ν•˜κ±°λ‚˜ λͺ¨ν‚Ήν•  수 μžˆμ–΄ ν…ŒμŠ€νŠΈκ°€ μš©μ΄ν•΄μ§.
정적 λ©”μ„œλ“œ μ‚¬μš©μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œλ‘œ λ³€κ²½ν•˜κ±°λ‚˜, ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ ꡬ쑰둜 변경정적 λ©”μ„œλ“œλŠ” μƒνƒœλ₯Ό κ°€μ§€μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈκ°€ μ–΄λ €μšΈ 수 있음. 이λ₯Ό μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œλ‘œ λ³€κ²½ν•˜κ±°λ‚˜, 정적 λ©”μ„œλ“œλ₯Ό κ°μ‹ΈλŠ” 클래슀λ₯Ό μ‚¬μš©ν•˜μ—¬ μœ μ—°μ„±μ„ λ†’μž„.
μ™ΈλΆ€ 라이브러리λ₯Ό 직접 μ‚¬μš©μ™ΈλΆ€ 라이브러리λ₯Ό κ°μ‹ΈλŠ” 래퍼 클래슀λ₯Ό λ§Œλ“€μ–΄ μ‚¬μš©μ™ΈλΆ€ λΌμ΄λΈŒλŸ¬λ¦¬μ— λŒ€ν•œ 직접적인 μ˜μ‘΄μ„±μ„ 쀄이고, λ³€κ²½ μ‹œμ—λ„ 래퍼 클래슀만 μˆ˜μ •ν•˜λ©΄ λ˜λ―€λ‘œ μœ μ§€λ³΄μˆ˜μ„±μ΄ ν–₯μƒλ˜λ©°, λͺ¨ν‚Ήμ„ 톡해 ν…ŒμŠ€νŠΈλ„ μ‰¬μ›Œμ§.
μ‹€ν–‰ μ‹œμ μ— 따라 λ‹¬λΌμ§€λŠ” κ²°κ³Όν…ŒμŠ€νŠΈν•˜κ³  싢은 μ½”λ“œ 뢄리, 예츑 κ°€λŠ₯ν•œ μž…λ ₯κ³Ό 좜λ ₯으둜 λ³€κ²½μ‹€ν–‰ μ‹œμ μ— 따라 λ‹¬λΌμ§€λŠ” 뢀뢄을 λΆ„λ¦¬ν•˜μ—¬ 예츑 κ°€λŠ₯ν•œ κ²°κ³Όλ₯Ό μ œκ³΅ν•˜λ„λ‘ μ„€κ³„ν•˜λ©΄ ν…ŒμŠ€νŠΈμ˜ 일관성을 μœ μ§€ν•  수 있음. 특히, μ‹œκ°„μ΄λ‚˜ 랜덀 값을 λΆ„λ¦¬ν•˜λŠ” 것이 μ€‘μš”.
역할이 μ„žμ—¬ μžˆλŠ” μ½”λ“œκ° 역할을 λ‹΄λ‹Ήν•˜λŠ” 클래슀둜 λΆ„λ¦¬ν•˜κΈ°λ‹¨μΌ μ±…μž„ 원칙(SRP)을 μ μš©ν•˜μ—¬ μ½”λ“œλ₯Ό λΆ„λ¦¬ν•˜λ©΄ 각 역할에 λŒ€ν•œ ν…ŒμŠ€νŠΈκ°€ λͺ…ν™•ν•΄μ§€κ³ , μ½”λ“œμ˜ 가독성과 μœ μ§€λ³΄μˆ˜μ„±μ΄ ν–₯상됨.
μ‹œκ°„μ΄λ‚˜ μž„μ˜ κ°’ 생성 κΈ°λŠ₯ ν¬ν•¨μ‹œκ°„μ΄λ‚˜ μž„μ˜ κ°’ 생성을 μΈν„°νŽ˜μ΄μŠ€λ‘œ μΆ”μƒν™”ν•˜μ—¬ μ£Όμž… λ°›κΈ°μ‹œκ°„μ΄λ‚˜ 랜덀 κ°’ 생성 λ‘œμ§μ„ μΈν„°νŽ˜μ΄μŠ€λ‘œ λΆ„λ¦¬ν•˜κ³  μ£Όμž…λ°›μœΌλ©΄, ν…ŒμŠ€νŠΈ μ‹œ μ›ν•˜λŠ” 값을 μ‰½κ²Œ λͺ¨ν‚Ήν•  수 μžˆμ–΄ 예츑 κ°€λŠ₯ν•œ ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯해짐.
λ³΅μž‘ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 ν¬ν•¨λœ λ©”μ„œλ“œλ³΅μž‘ν•œ λ‘œμ§μ„ μž‘μ€ λ‹¨μœ„λ‘œ λΆ„λ¦¬ν•˜κ³ , 각각을 κ°œλ³„μ μœΌλ‘œ ν…ŒμŠ€νŠΈν•˜κΈ°λ³΅μž‘ν•œ λ‘œμ§μ€ μž‘μ€ λ©”μ„œλ“œλ‘œ λΆ„λ¦¬ν•˜μ—¬ 각각의 λ©”μ„œλ“œλ₯Ό κ°œλ³„μ μœΌλ‘œ ν…ŒμŠ€νŠΈν•  수 μžˆλ„λ‘ 섀계. μ΄λŠ” 디버깅과 μ½”λ“œ 이해도λ₯Ό 높이고, ν…ŒμŠ€νŠΈλ₯Ό 보닀 μ² μ €ν•˜κ²Œ μˆ˜ν–‰ν•  수 있게 함.
비동기 μ½”λ“œ, 콜백 μ‚¬μš©λΉ„λ™κΈ° μ½”λ“œλ₯Ό 동기 μ½”λ“œλ‘œ κ°μ‹Έκ±°λ‚˜, 비동기 ν˜ΈμΆœμ„ μΆ”μƒν™”ν•˜μ—¬ ν…ŒμŠ€νŠΈν•˜κΈ°λΉ„λ™κΈ° μ½”λ“œμ˜ ν…ŒμŠ€νŠΈλŠ” μ–΄λ €μšΈ 수 μžˆμœΌλ―€λ‘œ, λ™κΈ°μ μœΌλ‘œ λ™μž‘ν•˜κ²Œ ν•˜κ±°λ‚˜ μΆ”μƒν™”λœ μΈν„°νŽ˜μ΄μŠ€λ‘œ κ°μ‹Έμ„œ ν…ŒμŠ€νŠΈν•  수 μžˆλ„λ‘ 섀계.
파일, λ„€νŠΈμ›Œν¬, DB와 같은 μ™ΈλΆ€ λ¦¬μ†ŒμŠ€μ— μ˜μ‘΄ν•˜λŠ” μ½”λ“œμ™ΈλΆ€ λ¦¬μ†ŒμŠ€ 접근을 μΈν„°νŽ˜μ΄μŠ€λ‘œ μΆ”μƒν™”ν•˜κ³ , ν…ŒμŠ€νŠΈ μ‹œ λͺ¨μ˜ 객체(mock)λ₯Ό μ‚¬μš©μ™ΈλΆ€ λ¦¬μ†ŒμŠ€μ— μ˜μ‘΄ν•˜λŠ” μ½”λ“œλ₯Ό μΆ”μƒν™”ν•˜μ—¬ μ˜μ‘΄μ„±μ„ 쀄이고, ν…ŒμŠ€νŠΈ μ€‘μ—λŠ” λͺ¨μ˜ 객체λ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ ν…ŒμŠ€νŠΈκ°€ λ…λ¦½μ μœΌλ‘œ μ΄λ£¨μ–΄μ§ˆ 수 있게 함.
κ·Έ μ™Έ ν…ŒμŠ€νŠΈν•˜κΈ° μ–΄λ €μš΄ μ½”λ“œλ‹¨μΌ μ±…μž„ 원칙(SRP)을 μ μš©ν•˜μ—¬ μ½”λ“œ 뢄리, ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ μ½”λ“œλ‘œ λ¦¬νŒ©ν† λ§λ‹¨μΌ μ±…μž„ 원칙을 μ μš©ν•˜λ©΄ μ½”λ“œλ₯Ό λͺ…ν™•ν•˜κ²Œ 뢄리할 수 μžˆμ–΄, 각 뢀뢄을 λ…λ¦½μ μœΌλ‘œ ν…ŒμŠ€νŠΈν•˜κ³  μœ μ§€λ³΄μˆ˜ν•˜λŠ” 것이 μš©μ΄ν•΄μ§.
profile
μ–΄μ œμ˜ λ‚˜λ³΄λ‹€ μ„±μž₯ν•œ μ‚¬λžŒμ΄ 될 수 μžˆλ„λ‘ λ…Έλ ₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

0개의 λŒ“κΈ€