2jigoo / BookStudy-StartTdd

'테스트 주도 개발 시작하기' 스터디
2 stars 0 forks source link

[7주차-1] 테스트 코드와 유지보수 (1/2) - 2jigoo #29

Closed 2jigoo closed 1 year ago

2jigoo commented 1 year ago

Chapter 10. 테스트 코드와 유지보수 (1/2)

스터디 일시 2023.09.14

목표

2jigoo commented 1 year ago

Chapter 10. 테스트 코드와 유지보수

테스트 코드와 유지보수


변수나 필드를 사용해서 기댓값 표현하지 않기

@Test
void dateFormat() {
    LocalDate date = LocalDate.of(1945, 8, 15);
    String dateText = formatDate(date);

    // X
    // - 문자열 연결이 있어 복잡
    // - date.getMonth()로 잘못 사용하면?
    assertEquals(date.getYear() + "년 " + 
            date.getMonthValue() + "월 " +
            date.getDayOfMonth() + "일", dateText);

    // O
    // 해당 테스트가 실패하면, formatDate 메서드만 확인하면 된다.
    assertEquals("1945년 8월 15일", dateText);
}
// 필드 또는 변수 사용 X
// 테스트가 실패했을 때, 사용했던 필드나 변수가 무엇인지 확인해야하는 번거로움
// 값을 바로 사용하면 직관적으로 확인할 수 있음

// private List<Integer> answers = Arrays.asList(1, 2, 3, 4);
// private Long respondentId = 100L;

@Test
public void saveAnswerSuccessfully() {
    // 답변할 설문이 존재
    Survey survey = SurveyFactory.createApporvedSurvely(1L);
    surveyRepository.save(survey);

    SurveyAnswerRequest surveyAnswer = SurveyAnswerRequest.builder()
            .surveyId(1L)
            .respondentI2(100L)
            .answers(Arrays.asList(1, 2, 3, 4))
            .build();

    svc.answerSurvey(surveyAnswer);

    // 저장 결과 확인
    SurveyAnswer savedAnswer = memoryRepository.findBySurveyAndRespondent(1L, 100L);

    assertAll(
        () -> assertEquals(100L, savedAnswer.getRespondentId),
        () -> assertEquals(4, savedAnswer.getAnswers().size()),
        () -> assertEquals(1, savedAnswer.getAnswers().get(0)),
        () -> assertEquals(2, savedAnswer.getAnswers().get(1)),
        () -> assertEquals(3, savedAnswer.getAnswers().get(2)),
        () -> assertEquals(4, savedAnswer.getAnswers().get(3))
    )
}


두 개 이상을 검증하지 않기


정확하게 일치하는 값으로 모의 객체 설정하지 않기

@Test
void weakPassword() {
    // 약한 암호인 경우, UserRegister가 의도한 대로 동작하는지를 확인하는 테스트
    // - "pw"를 넘겼을 때, 약한 암호인지 확인하는 코드가 아님
    // - 임의의 문자열에 대해 true를 리턴해도 이 테스트 의도에 전혀 문제되지 않음!

    // X
    // BDDMockito.given(mockPasswordChecker.checkPasswordWeak("pw")) 
    BDDMockito.given(mockPasswordChecker.checkPasswordWeak(Mockito.anyString()))
            .willReturn(true);

    assertThrows(WeakPasswordException.class, () -> {
        userRegister.register("id", "pw", "email");
    });
}
@Test
void checkPassword() {
    userRegister.register("id", "pw", "email");
    BDDMockito.then(mockPasswordChecker)
            .should()
            // .checkPasswordWeak("pw");
            .checkPasswordWeak(Mockito.anyString());
}
2jigoo commented 1 year ago

과도하게 구현 검증하지 않기

@Test
void checkPassword() {
    userRegister.register("id", "pw", "email");

    // PasswordChecker#checkPasswordWeak() 메서드 호출 여부 검사
    BDDMockito.then(mockPasswordChecker)
            .should()
            .checkPasswordWeak(Mockito.anyString());

    // UserRepository#findById() 메서드를 호출하지 않는 것을 검사
    BDDMockito.then(mockRepository)
            .should(Mockito.never())
            .findById(Mockito.anyString());
}
// 가짜 구현으로 대체 하기 어려운 레거시 코드
public void changeEmail(String id, String email) {
    int cnt = userDao.countById(id);
    if (cnt == 0) {
        throw new NoUserException();
    }

    userDao.updateEmail(id, email);
}
@Test
void changeEmailSuccessfully() {
    given(mockDao.countById(Mockito.anyString())).willReturn(1);

    // 이메일 수정 시
    emailService.changeEmail("id", "new@somehost.com");

    // updateEmail 메서도 호출 검증
    then(mockDao)
            .should()
            .updateEmail(Mockito.anyString(), Mockito.matches("new@somehost.com"));
}

기능을 검증할 수단이 구현 검증뿐이라면 모의 객체를 사용해서 테스트 코드 작성. 테스트 코드 작성 후엔 점진적으로 코드를 리팩토링하여 구현이 아닌 결과를 검증할 수 있도록 해야한다.


셋업을 이용해서 중복된 상황을 설정하지 않기

@BeforeEach와 같은 셋업 메서드를 이용해 테스트 메서드의 상황 설정을 지양한다.

테스트 메서드는 검증을 목표로 하는 하나의 완전한 프로그램이어야 한다. 각 테스트 메서드는 검증 내용을 스스로 잘 설명할 수 있어야 한다.