테스트 코드를 이용한 객체 동작 검증 방법 및 관련 개념
강의 내용 복습 : 김영한의 스프링 입문 강의 (섹션 3)
🔔 강의 내용
📌 Java에서의 테스트 코드 작성
- Java에서 개발한 웹 애플리케이션 기능은 컨트롤러의 메인 메서드에서 실행됩니다.
- 이와는 달리 테스트 코드는 웹 애플리케이션의 기능을 반복 또는 다중 실행하기 위해 사용됩니다.
- 기능 테스트 과정에서 버그 또는 변경사항에 따른 사이드 이펙트를 조기에 발견할 수 있습니다.
- Java의 대표적인 테스트 프레임워크는 JUnit의 Test API가 있습니다.
📌 MemoryMemberRepository 객체 테스트
- 메모리 공간이 인터페이스와 잘 연결되었는지, 코드가 잘 동작되는지 확인해야 됩니다.
- 이를 위해 Member 객체와 JUnit의 Test, AfterEach, BeforeEach API가 사용되었습니다.
- Member 객체의 name 데이터에 임의의 이름으로 member1, member2를 저장하였습니다.
- 최종적으로 데이터를 리스트화 시키고 데이터의 개수를 파악하였습니다.
- MemoryMemberRepository 객체의 테스트 코드에 대한 상세한 설명은 글 하단에 작성하였습니다.
📌 MemberService 객체 생성
- 회원 관리를 위한 비즈니스 로직 내용을 포함한 서비스 객체를 생성하였습니다.
- MemberService 객체는 MemberRepository 인터페이스에 의존합니다.
- 그리고 해당 의존성은 생성자 메서드를 통해 외부에서 주입(DI)되게 설정하였습니다.
- DI의 일환으로 MemberService 객체 생성 시 MemberRepository에 할당하도록 설정하였습니다.
- MemberService 객체에 대한 상세한 설명은 글 하단에 작성하였습니다.
📌 MemberService 객체 테스트
- MemberService 객체의 테스트 코드에 대한 상세한 설명은 글 하단에 작성하였습니다.
🔔 개념 보충
📌 Dependency Injection
- 의존성 주입 관련된 개념은 다음 블로그 글에 작성하였습니다.
- 해당 링크로 들어가시면 @Controller, @Service, @Repository에 대한 설명이 있습니다.
- 또한 스프링 구축 시 @Component 및 @Autowired의 관계에 대한 내용도 있습니다.
📌 @AfterEach
- JUnit 라이브러리의 대표 메서드 중 하나입니다.
- 테스트 간의 충돌 방지를 위해 테스트 후 저장소를 비우도록 설정하는 메서드입니다.
- 이는 테스트가 실행된 이후마다 적용되며 테스트 코드끼리 서로 의존하지 않게 됩니다.
📌 @BeforeEach
- JUnit 라이브러리의 대표 메서드 중 하나입니다.
- 테스트가 실행될 때마다 새로운 메서드가 생성되도록 설정할 수 있는 메서드입니다.
- 생성된 객체의 인스턴스를 다른 객체에 의존성 주입으로 제공하도록 설정할 수 있습니다.
- BeforeEach 과정을 거치면 각 테스트가 실행될 때 데이터가 중복되지 않게 됩니다.
📌 assertThat
- AssertJ 라이브러리의 메서드 중 하나입니다.
- assertThat 메서드는 실제값과 기댓값을 비교하는 데 사용됩니다.
- 메서드 사용 방법의 예시로는
assertThat(actual).isEqualTo(expected)
가 있습니다.- 여기서 actual 값이 expected 값과 동일한지 비교합니다.
📌 isEqualTo
- AssertJ 라이브러리의 메서드 중 하나로 보통 assertThat 메서드와 함께 사용됩니다.
- 객체의 실체값이 기댓값과 일치하는지 검사하기 위해 사용됩니다.
📌 assertThrows
- AssertJ 라이브러리의 메서드 중 하나입니다.
- assertThrows 메서드는 특정 조건에서 예외적인 값이 발생되는지 검사하는 데 사용됩니다.
- 메서드 사용 방법의 예시는 아래와 같습니다.
assertThrows(IllegalStateException.class, () -> { /* 테스트 코드 */ })
- 예시의 테스트 코드 실행 시 IllegalStateException이 발생되는지 검사할 수 있습니다.
📌 IllegalStateException
- 메서드가 적절하지 않은 호출을 받았을 때 발생되는 런타임 예외입니다.
- 예를들어 아직 객체 초기화가 되지 않았거나 소비된 상태에서 사용하려고 시도할 때 발생됩니다.
- 따라서 메서드가 요구하는 객체의 상태를 충족하는지에 대한 여부를 검사할 수 있습니다.
🔔 로직 이해
📌 MemoryMemberRepository 객체의 테스트 코드
- 아래는 MemoryMemberRepositoryTest의 전체 코드이며 관련 설명을 주석으로 작성하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
// 테스트 간의 충돌을 방지하기 위해 테스트가 종료될 때마다 저장소를 비우도록 설정하였습니다.
@Test
public void save() {
// given
Member member = new Member();
member.setName("Spring");
// Spring이라는 name의 새로운 Member 객체를 생성하였습니다.
// when
repository.save(member);
// then
Member result = repository.findById(member.getId()).get();
assertThat(result).isEqualTo(member);
// Member 객체의 저장된 id와 조회한 id가 일치하는지 확인하였습니다.
}
@Test
public void findByName() {
// given
Member member1 = new Member();
member1.setName("Spring1");
repository.save(member1);
// given
Member member2 = new Member();
member2.setName("Spring2");
repository.save(member2);
// 다른 name 값을 가진 회원(member1, member2) 객체를 생성하고 저장소에 저장하였습니다.
// when
Member result = repository.findByName("Spring1").get();
// 객체의 name 값이 Spring1인 Member 객체를 조회하였습니다.
// then
assertThat(result).isEqualTo(member1);
// Spring1 name으로 조회된 객체가 member1인지 확인하였습니다.
}
@Test
public void findAll() {
// given
Member member1 = new Member();
member1.setName("Spring1");
repository.save(member1);
// given
Member member2 = new Member();
member2.setName("Spring2");
repository.save(member2);
// name 값이 다른 2개의 회원 객체를 생성하였고 저장소에 저장하였습니다.
// when
List<Member> result = repository.findAll();
// findAll 메서드로 저장소에 저장된 모든 회원의 리스트를 조회하였습니다.
// then
assertThat(result.size()).isEqualTo(2);
// 조회된 리스트의 크기가 2인지 조회하였습니다.
}
}
📌 MemberService 객체
- 아래는 MemberService 객체의 전체 코드이며 관련 설명을 주석으로 작성하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.repository.MemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository;
// MemberService 객체에 의존성을 주입할 수 있도록 변경
public MemberService(MemberRepository memberRepository) {
// MemberService 객체가 MemberRepository 인터페이스에 의존하도록 설정하였습니다.
// 해당 의존성은 생성자 메서드를 통해 외부에서 주입되게 설정하였습니다.
this.memberRepository = memberRepository;
}
// 회원가입 기능
public Long join(Member member) {
validateDuplicateMember(member);
// 주어진 Member 객체의 데이터가 저장소에 이미 존재하는지 확인하도록 설정하였습니다.
// 내부 메서드인 validateDuplicateMember에 대한 로직은 하단 문단에 작성하였습니다.
memberRepository.save(member);
// Member 객체의 데이터가 중복되지 않으면 MemberRepository를 통해 저장소에 Member 객체를 저장하도록 설정하였습니다.
return member.getId();
// 최종적으로 로직에 따라 저장된 Member의 id를 반환하도록 설정하였습니다.
}
// 저장소의 Member 객체 중복 여부 검증
// 해당 검증 로직은 내부 메서드로 join 메서드 내에서 호출됩니다.
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
// Member 객체의 저장소에 주어진 name이 있는지 검색합니다.
.ifPresent(m -> {
// 만일 저장소에 동일한 name의 Member 객체가 존재한다면 람다로 이어지게 설정하였습니다.
throw new IllegalStateException("이미 존재하는 회원입니다.");
// join 메서드에서 id를 반환하는 것이 아니라 IllegalStateException을 반환하도록 설정하였습니다.
});
}
// 전체 회원 조회 기능
public List<Member> findMembers() {
return memberRepository.findAll();
// 저장소의 findAll 메서드를 호출하여 전체 회원 리스트를 반환하게 설정하였습니다.
}
// 특정 회원 조회 기능
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
// 저장소의 findById 메서드를 호출하여 memberId에 해당되는 Member 객체를 Optional 타입으로 반환하도록 설정하였습니다.
}
}
📌 MemberService 객체의 테스트 코드
- 아래는 MemberServiceTest의 전체 코드이며 관련 설명을 주석으로 작성하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
// 각각의 테스트 실행 전 새로운 멤버 리포지토리 및 멤버 서비스 객체 생성
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
// 테스트가 실행될 때마다 새로운 MemoryMemberRepository를 생성하도록 설정하였습니다.
// 생성된 MemoryMemberRepository 인스턴스를 MemberService에 의존성 주입으로 제공하도록 설정하였습니다.
// 이 과정을 거치면 각 테스트 사이에 데이터가 중복되지 않게 됩니다.
}
// 각각의 테스트 실행 후 멤버 리포지토리에 저장된 내용 삭제
@AfterEach
public void afterEach() {
memberRepository.clearStore();
// MemoryMemberRepository에 저장된 모든 데이터를 삭제하도록 설정하였습니다.
}
// 회원가입 시나리오 테스트
@Test
public void MemberJoin() throws Exception {
// given
Member member = new Member();
member.setName("hello");
// 새로운 Member 객체를 생성하고 이름을 설정하도록 하였습니다.
// when
Long saveId = memberService.join(member);
// join 메서드는 Member 객체를 매개변수로 전달받습니다.
// 전달받은 Member 객체를 MemoryMemberService에 저장합니다.
// Member 객체 저장이 성공적으로 완료되면 id가 반환됩니다.
// 반환된 id는 long 타입의 saveId 변수에 저장됩니다.
// then
Member findMember = memberRepository.findById(saveId).get();
// saveId 변수에 저장된 Member의 id로 회원 정보를 조회합니다.
assertEquals(member.getName(), findMember.getName());
// 입력한 Member의 name과 저장 후 조회된 회원 name이 일치하는지 검토합니다.
}
// 중복 회원 예외 처리 시나리오 테스트
@Test
public void ExceptionDuplicatedMember() throws Exception {
// given
Member member1 = new Member();
member1.setName("spring");
// given
Member member2 = new Member();
member2.setName("spring");
// 동일한 name을 가진 member1, member2 변수를 생성하였습니다.
// when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));
// 첫 번째 회원인 member1은 정상적으로 회원가입시킵니다.
// 하지만 member2의 경우 회원가입 시 IllegalStateException이 발생되는지 검사합니다.
// then
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// 중복 검사 결과 예외가 발생되었을 때 발생된 메시지가 "이미 존재하는 회원입니다."와 일치하는지 조회하도록 설정하였습니다.
// 이는 중복 회원 가입 시 예외 처리가 올바르게 동작하는지 검증하는 것입니다.
}
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.