티스토리 뷰
[SpringBoot/GDSC] 회원 관리 예제 - 백엔드 개발 / Test 자동 생성과 오류 해결 방법 @AfterEach
YouJungJang 2023. 10. 3. 02:04본 포스팅은 인프런 김영한 강사님의 스프링 부트 입문 강의 섹션 3 <회원 관리 예제 - 백엔드 개발>을 수강하고 배운 점을 정리했습니다.
[1] 회원 가입 메서드에서 '중복 회원 검색' 코드의 발전 과정
hello\hellospring\service\MemberService에서 회원 가입 메서드인 'join'의 코드 발전 과정을 살펴보자
우선 join의 초기 모습은 아래와 같다.
첫 번째 줄에 Optional에 주목하자.
개발을 할 때 가장 많이 발생하는 예외 중 하나가 바로 NPE(NullPointerException)이다.
NPE를 피하기 위해 과거에는 일일이 'If(null)...'이런 식으로 null 여부를 검사하는 코드를 추가했는데, 이는 프로그램 크기가 클수록 번거롭고 복잡하다. 이때 Java8에서 등장한 Optional <T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와주고 유용한 메서드도 제공한다.
여기서 우리가 사용한 메서드는 'ifPresent'로 Optional 객체가 Null값이 아닌 존재할 경우라면 조건문을 실행하는 메서드이다.
첫 번째에서 Optional이 보기 안 좋다면 선언부를 지우고 바로 사용해도 무방하다. 첫 번째 코드에서 선언부 오른편에 위치했던 memberRepository.findByName(member.getName())에 바로 ifPresent를 붙여서 코드를 이었다.
두 번째 코드의 단점은 이어붙인 코드가 너무 길어서 눈에 명확하게 안 들어오는 점이다. 이를 해결하기 위해 findByName을 따로 메서드로 만들었다. validateDuplicateMember 메서드를 사용해서 코드를 간결하게 만들었다.
[2] Test 코드 자동 생성 하기
개발한 기능을 실행해서 테스트할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 사용한다. 하지만 이러한 방법은 준비부터 실행하는데 까지 오래 걸리고, 반복 실행이 어려우며, 여러 테스트를 한 번에 실행하기 어렵다는 단점이 있다. 자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.
1) (Window 기준) 테스트하고 싶은 클래스 이름에 마우스를 대면 형광등 모양이 나온다. 형광등을 누르면 여러 옵션들이 나오는데 이 중에서 'Create Test'를 선택한다.
2) Create Test 창에서는 우리가 테스트할 클래스인 'MemberService'의 멤버 메서드들 중 어떤 것의 테스트 코드를 생성하고 싶은지 선택할 수 있다. 모두 선택한 뒤, OK를 누른다.
3) 그러면 이렇게 Service 패키지 안에 MemberServiceTest라는 자바 클래스가 자동 생성된다.
4) Test를 위한 메서드는 아래와 같이 구성하면 된다.
차례대로 given, when, then 세 단계로 구성하는 것이 이상적인 테스트 코드이다. 첫 번째 'given'은 '주어진 조건'으로 기반이 되는 데이터를 제시한다. 다음 'when' 부분은 '테스트가 실행되는 상황'을 작성한다. 즉, 검증하고자 하는 상황을 입력한다. 마지막으로 'then'에서는 테스트가 성공한다면 도출될 '올바른 결과'를 제시한다.
예시 코드는 아래와 같다.
@Test
public void 회원가입() throws Exception {
//Given
Member member = new Member();
member.setName("hello");
//When
Long saveId = memberService.join(member);
//Then
Member findMember = memberRepository.findById(saveId).get();
assertEquals(member.getName(), findMember.getName());
}
+ 테스트 코드는 내부적으로 실행하는 것이므로 명확성을 위해 위와 같이 메서드명을 한국어로 입력해도 무방하다고 한다.
[3] Test 코드 순차 시행 미 보장으로 인한 오류와 해결 방법: @AfterEach
한 번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트의 결과가 남을 수 있다. 이렇게 되면 이전 테스트 때문에 다음 테스트가 실패할 가능성이 있다. 아래의 예시와 함께 이해해 보자.
첫 번째 코드는 'MemoryMemberRepositoryTest' 클래스에 있는 'findAll()' 테스트 메서드이다.
@Test
public void findAll() {
//given
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//when
List<Member> result = repository.findAll();
//then
assertThat(result.size()).isEqualTo(2);
}
아래에 두 번째 코드는 'MemoryMemberRepositoryTest' 클래스에 있는 'findByName()' 테스트 메서드이다.
@Test
public void findByName() {
//given
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//when
Member result = repository.findByName("spring1").get();
//then
assertThat(result).isEqualTo(member1);
}
두 메서드는 모두 'MemoryMemberRepositoryTest'에 있고, 'given' 조건부에서 테스트를 위해 멤버 객체를 생성해 레파지토리에 세이브하는 코드를 구성하는 공통점이 있다. 이때, 각 매서드에서는 멤버 이름을 "spring1"과 "spring2"로 똑같이 생성했다.
이 테스트를 돌린다면, 각 테스트 메서드들의 실행 순서를 보장하지 않으므로 메모리 DB에 직전 테스트의 결과가 남을 수 있다. 그럼 뜻하지 않게 이전 테스트로 인해 다음 테스트가 실패한다. (같은 이름의 멤버가 이미 DB에 존재하므로)
이를 해결하기 위해 테스트 하나가 끝나면 데이터 레파지토리를 지워주는 코드를 넣어야 한다. 이것이 바로 @AfterEach이다. @AfterEach를 사용하면 각 테스트가 종료될 때마다 이 기능을 실행한다. 여기서는 메모리 DB에 저장된 데이터를 삭제한다.
+ 테스트는 각각 독립적으로 실행되어야 한다. 테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아니다.