해결된 질문
작성
·
194
·
수정됨
1
"회원 리포지토리 테스트 케이스 작성" 강의 중 12분쯤 findByName()을 실행할 때 findAll()에서 만든 member1,member2가 불러와져서 테스트를 통과하지 못하고 하셨는데 이 부분이 이해가 잘 가지 않습니다.
findByName()의 assertThat(result).isEqualTo(member1);
부분에서 예외가 발생합니다.
그런데 왜 첫번째 줄 Member member1 = new Member();
에서 실행을 멈추지 않고 실행이 계속되나요?
이미 같은 변수이름(findAll()에서 만들어진 member1, member2)이 있음에도 불구하고 예외가 발생해야 맞는데 왜 정상적으로 실행되어서 assertThat(result).isEqualTo(member1);
까지 내려가서 예외가 발생하나요?
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);
}
}
그리고 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에서 조회한 데이터 중 발견한 데이터를 반환할 뿐입니다.
예외가 발생한다고 생각한 부분은 service코드인 join()과 착각하신것 인지요?
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);
}
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
확인한 테스트 결과를 바탕으로 정리하면 이전 테스트가 다음 테스트에 영향을 미쳤고, 테스트 실행 순서에 따라 결과가 달라질 수 있음을 확인할 수 있습니다.
감사합니다.
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
를 포함하여 올바른 리셋 로직이 구현되어 있는데도 문제가 해결되지 않는다면, 추가적인 정보를 제공해주시면 더욱 정확한 진단이 이뤄질 수 있습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
감사합니다. 이해에 큰 도움이 되었습니다!