인프런 커뮤니티 질문&답변

liltjay님의 프로필 이미지
liltjay

작성한 질문수

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

회원 리포지토리 테스트 케이스 작성 강의 중 findByName() 질문

해결된 질문

작성

·

194

·

수정됨

1

"회원 리포지토리 테스트 케이스 작성" 강의 중 12분쯤 findByName()을 실행할 때 findAll()에서 만든 member1,member2가 불러와져서 테스트를 통과하지 못하고 하셨는데 이 부분이 이해가 잘 가지 않습니다.

  1. findByName()의 assertThat(result).isEqualTo(member1); 부분에서 예외가 발생합니다.
    그런데 왜 첫번째 줄 Member member1 = new Member();에서 실행을 멈추지 않고 실행이 계속되나요?

이미 같은 변수이름(findAll()에서 만들어진 member1, member2)이 있음에도 불구하고 예외가 발생해야 맞는데 왜 정상적으로 실행되어서 assertThat(result).isEqualTo(member1);까지 내려가서 예외가 발생하나요?

 

  1. findByName()의 assertThat(result).isEqualTo(member1);가 각각 어떤 객체인지 헷갈립니다.
    member1은 findAll()에서 만들어진 것이고, result는 findByName()의 첫번째 줄에서 만들어진건가요?
    이것도 이해가 가지 않는게 clear()가 없는 상태에서 member1을 또 만들면 이름이 중복되는데 왜 예외가 발생하지 않나요??

 

package hello.hellospring.repository;

class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member(); 
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }
}

 

  1. 그리고 memberService.join(member1);

package hello.hellospring.service;

class MemberServiceTest {

    MemberService memberService = new MemberService();

    @Test
    void 회원가입() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

    }
}

회원 서비스 테스트 강의에서도 비슷한 질문이 있습니다. 11분 50초 경에 memberService.join(member1);에서 예외가 발생한다고 하셨는데 그 이유를 잘 모르겠습니다.
member1, member2의 이름을 spring으로 만들고나서 member1을 join할 때는 레포지토리에 아무런 값이 없으므로 예외가 발생할 이유가 있나요? 오히려 assertThrows(IllegalStateException.class, () -> memberService.join(member2)); 에서 중복으로 인해 예외가 발생해야하지 않나요?

답변 2

2

안녕하세요. liltjay님, 공식 서포터즈 OMG입니다.

1번) findByName()은 예외를 던지지 않습니다. Map에서 조회한 데이터 중 발견한 데이터를 반환할 뿐입니다.

image

예외가 발생한다고 생각한 부분은 service코드인 join()과 착각하신것 인지요?

image

join()의 내부 로직 중 repository에서 조회 한 데이터 중 이미 존재하는 데이터가 존재하는 경우, 예외 발생하는 코드와 혼선이 있으신 것 같습니다.

 

2번) 이 부분의 이해를 돕기 위해 다음과 같은 코드 예시를 들겠습니다. 아래의 코드를 직접 실행해보실 것을 권장드립니다.

 

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();
    
    @Test
    @Order(1)
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        assertThat(result).isEqualTo(member1);
    }

    @Test
    @Order(2)
    public void findAll() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }
}

위의 코드는 테스트 순서를 핸들링 하기 위해 @TestMethodOrder를 사용하였고 findByName()과 findAll() 테스트의 순서의 우선순위를 변경하여 테스트 하였습니다.

 

 

findByName의 Order가 1이고, findAll 이 2 인 경우

이 때, findByName()에서는 result와 member1은 같은 객체입니다. 따라서 테스트는 성공하게 됩니다.

하지만 findAll()에서는이전 테스트인 findByName()에서 저장한 데이터 때문에 실패하게 됩니다.

 

그리고 테스트 실행 순서를 서로 바꾸고, 각 객체의 참조값을 확인하기 위해 추가로 참조값 출력하는 코드를 추가하였습니다.

@Test
@Order(2)
public void findByName() {
    Member member1 = new Member();
    member1.setName("spring1");
    repository.save(member1);
    System.out.println("findByName.member1 = " + member1);

    Member member2 = new Member();
    member2.setName("spring2");
    repository.save(member2);
    System.out.println("findByName.member2 = " + member2);

    Member result = repository.findByName("spring1").get();

    assertThat(result).isEqualTo(member1);
}

@Test
@Order(1)
public void findAll() {
    Member member1 = new Member();
    member1.setName("spring1");
    repository.save(member1);
    System.out.println("findAll.member1 = " + member1);
    Member member2 = new Member();
    member2.setName("spring2");
    repository.save(member2);
    System.out.println("findAll.member2 = " + member2);

    List<Member> result = repository.findAll();

    assertThat(result.size()).isEqualTo(2);
}

 

image

findAll에서 저장한 member1의 참조값 앞 2자리는 5e입니다.

findAll에서 저장한 member2의 참조값 앞 2자리는 3b입니다.

findByName에서 저장한 member1의 참조값 앞 2자리는 6e입니다.

findByName에서 저장한 member2의 참조값 앞 2자리는 34입니다.

이 참조값 결과를 바탕으로 테스트 실패 메시지를 보면,

findByName()테스트에서 기대한 결과는 6e 즉, findByName()의 member1이지만,

hello.hellospring.domain.Member@6e4784bc

 

실제 데이터는 이전 테스트의 findAll() member1 5e인 것을 확인할 수 있습니다.

hello.hellospring.domain.Member@5ea434c8

 

확인한 테스트 결과를 바탕으로 정리하면 이전 테스트가 다음 테스트에 영향을 미쳤고, 테스트 실행 순서에 따라 결과가 달라질 수 있음을 확인할 수 있습니다.

감사합니다.

liltjay님의 프로필 이미지
liltjay
질문자

감사합니다. 이해에 큰 도움이 되었습니다!

0

안녕하세요, 인프런 AI 인턴입니다. liltjay님, 관련하여 비슷한 상황에서 발생하는 문제점과 해결 방법을 찾아봤습니다.

JUnit에서 각 테스트 메소드는 독립적으로 실행되어야 합니다. 하지만 MemoryMemberRepository 인스턴스가 각 테스트 메소드에 대해 새로 생성되지 않으면, 하나의 테스트 메소드에서 repository에 멤버를 저장하고 나면 다른 테스트 메소드에서도 이들 멤버가 repository에 남아있을 수 있습니다. 이런 상황을 방지하기 위해 일반적으로 @AfterEach 어노테이션이 사용되며, 이를 통해 각 테스트 이후에 repository의 상태를 초기화하는 방법을 채택합니다.

문제가 된 부분은 assertThat(result).isEqualTo(member1); 코드입니다. 여기서 member1 객체와 result 객체가 동일한 객체인지 (같은 메모리 주소를 가리키는지) 비교하고, 이것이 실패하면 AssertionError가 발생합니다. Java에서 같은 이름을 가진 변수는 동일한 함수 또는 블록 내에서 선언할 수 없으나 클래스나 메소드가 다르면 문제되지 않습니다.

가장 흔한 오류의 원인으로는 @AfterEach 를 빼먹어서 발생하는 상태 오염(state pollution)이 있을 수 있습니다. 두 번째로는 findByName 메소드가 올바르게 작성되지 않았거나, Member클래스의 setName, getName, equals 같은 메소드의 구현에 문제가 있을 수 있습니다.

유사한 질문과 답변 게시물 중 아래 링크에서 비슷한 문제가 발생했으며, 결국 Member 객체의 setName 메소드 구현 문제로 해결된 사례가 있습니다:

이러한 유사한 상황에서는 각 테스트 메소드의 독립성을 유지하고, Member 객체 또는 MemoryMemberRepository의 메소드 구현을 다시 검토하는 것이 중요합니다. 테스트가 실패하는 정확한 이유를 알기 위해서는 실패하는 테스트의 스택 트레이스나 에러 메시지의 세부 정보가 필요할 수도 있습니다.

혹시 질문자님의 코드에서 구현의 문제가 발견되지 않고 @AfterEach를 포함하여 올바른 리셋 로직이 구현되어 있는데도 문제가 해결되지 않는다면, 추가적인 정보를 제공해주시면 더욱 정확한 진단이 이뤄질 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

liltjay님의 프로필 이미지
liltjay

작성한 질문수

질문하기