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

박연우님의 프로필 이미지

작성한 질문수

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

회원 서비스 테스트

AfterEach 동작 과정 질문

작성

·

475

0

[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오) 예
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]
여기에 질문 내용을 남겨주세요.

안녕하세요. 질문이 있어 이렇게 글을 남기게 되었습니다.

13:00에서 MemberServiceTest.java의 코드

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @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);
        /*
        try { // 예외 처리
            memberService.join(member2);
            fail();
        } catch (IllegalStateException e) {
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }
        */
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        // then

    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

이때 테스트 코드가 잘 동작하는 이유를 모르겠습니다. MemberService로 선언한 memberService 안에는 다른 레포짙토리가 존재한다는 점 알고 있습니다. 아래 코드에서는 spring이라는 이름을 가진 회원을 회원가입하고, 중복_회원_예외를 처리하는데, 이때 memberService의 레포지토리를 활용합니다. 따라서 회원의 정보가 저장되는 것은 memverService 클래스 안의 memberRepository입니다. 하지만 afterEach 함수를 보면 그동안 회원의 정보를 저장했던 레포지토리가 아닌, 새로 선언한 memerRepository의 정보를 초기화하고 있습니다. 따라서 회원 정보를 저장한 곳과 초기화하는 곳이 다른데 어떻게 코드가 잘 작동하는지가 궁금합니다.

답변 4

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 박연우님

김태훈님께서 잘 말씀해주셨는데요. static에 대한 이해가 필요합니다.

 

static 없이 작동한 이유는 강의 영상에서는 memberService.join()을 사용하면서 검증과정에서도 같은 memberService.findOne()을 호출했기 때문입니다.

따라서 static이 없어도 같은 memberRepository를 사용하게 됩니다.

강의 메뉴얼에서는 검증시에 memberRepository.findOne()으로 검증하는데, 이렇게 되면 실패하는 것을 확인할 수 있습니다.

 

이 부분이 잘 이해가 되지 않는다면 자바 로드맵을 통해 자바 기본기를 다시 한번 정리하는 것을 추천드립니다.

https://www.inflearn.com/roadmaps/744

감사합니다.

박연우님의 프로필 이미지
박연우
질문자

답장이 늦어 정말 죄송합니다...! 그런데 memberRepository.findOne()으로 검증한다는 것이 어디 부분을 말씀하시는 걸까요...? 회원가입()에서 바꿔보았는데 memberRepository에는 findOne 함수가 없어서 오류가 발생합니다! 메뉴얼이 어떤 걸 말씀하시는 건지... 올려주신 강의 자료 말씀하시나요??

김영한님의 프로필 이미지
김영한
지식공유자

올려주신 코드 이미지에서 회원가입()을 보시면 다음 부분이 있습니다.

여기를 따라가면 내부에서 리포지토리를 호출하겠지요?

 

//then

Member findMember = memberService.findOne(saveId).get();

1

위 의문을 해결하려면 static이 무엇인지 알아야 합니다.

 

말씀하신 것처럼 memberService 내부에 있는 MemoryMemberRepository 객체와, 테스트코드에서 선언된 memoryMemberRepository 는 다른 객체입니다.

하지만 MemoryMemberRepository 내부를 살펴보면 "private static Map<Long, Member> store = new HashMap<>();" 이라는 코드가 존재하는데요.

여기에서 static이란 무엇이냐, 쉽게 말하면 static이 붙은 store라는 인스턴스를 프로그램 내에서 딱 하나만 존재하도록 만들어줍니다.

 

정리하면 모든 MemoryMemberRepository 객체들은 동일한 store 를 사용하고 있습니다.

 

코드의 흐름을 정리해드리면

  1. memoryMemberRepository.clearStore() 실행

  2. 모든 MemoryMemberRepository가 함께 사용하는 store 필드가 비워짐.

  3. 이후 중복_회원_예외() 메서드 실행 시 -> memberService.join() 메서드를 실행하면 비어있던 store에 데이터를 넣어줌.

 

이해 안가시는 부분 있으면 말씀해주세요!

박연우님의 프로필 이미지
박연우
질문자

답장 감사합니다. 강의 내용 중에서도 static에 대해 살짝 언급해주셨기 때문에 static을 삭제하고 다시 실행해보았는데, 똑같이 잘 실행되었습니다. 이것은 어떻게 동작하는 걸까요...?

static 키워드를 지우고 테스트를 돌려도 테스트가 성공적으로 수행된다는 의미이실까요?

Service 로직이랑 Test 로직, Repository 로직을 함께 올려주시면 자세하게 살펴보겠습니다.

0

박연우님의 프로필 이미지
박연우
질문자

'테스트에서 선언된 memberRepositorymemberService의 생성자로 전달하므로써' 라고 하셨는데 현재 코드에서는 생성자로 전달하고 있지 않나요? 여전히 이해가 되지 않습니다...

0

안녕하세요, 인프런 AI 인턴입니다.

박연우님께서 강의 중 MemberServiceTest에서 @AfterEach가 적용되는 과정과 코드가 잘 작동하는 이유에 대해 궁금해 하시는군요.

말씀하신 부분과 관련해서는 @AfterEach가 테스트 직후에 실행되어 테스트간의 데이터 격리를 보장하는 역할을 한다는 점과, 테스트에서 사용하는 memberRepository 인스턴스가 MemberService에서도 사용되게 하는 DI(Dependency Injection) 패턴의 적용에 주목하셔야 할 것 같습니다. 따라서, 테스트에서 선언된 memberRepositorymemberService의 생성자로 전달하므로써 두 객체가 동일한 memberRepository를 참조하게 됨으로써 동일한 저장소에 대해 작업을 수행합니다.

당장 찾아드린 질문 중에는 비슷한 상황을 다룬 MemberServiceTest.java 에서 오류가 나는 이유라는 질문이 있습니다. 여기에서도 @BeforeEach@AfterEach의 사용과 관련된 상황이 설명되어 있으니 참조하시면 좋을 것 같습니다.

이와 유사한 맥락에서 테스트 코드 실행 시 매번 독립적인 인스턴스가 제공되어야 하기 때문에 @BeforeEach에서 memberServicememberRepository를 새로 만들어주는 질문도 참고하실 수 있습니다.

추가적으로, 각 테스트마다 @AfterEach를 사용하여 메모리 내의 데이터를 정리해주는 것이 중요한데, 이는 각 테스트가 독립적으로 실행되어야 하므로 한 테스트의 결과가 다른 테스트에 영향을 미치지 않게 하기 위함입니다. 한 질문(MemberServiceTest.java AfterEach 질문)에서 이러한 점이 설명되었으며, 다른 테스트 메서드에서 MemberService를 호출할 때 발생할 수 있는 저장소의 상태 불일치 문제를 예방하기 위함입니다.

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