패키지
를 만든 뒤, 그 하위에 소스파일 집어 넣는다.한글 함수명
을 사용할 수 있다.package Hello.HelloSpring.Domain;
public class Member {
private String name;
private Long id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
도메인
영역으로, 회원의 멤버를 id
와 name
으로 선언하였으며, getter
와 setter
함수를 구현하였다.getter
, setter
함수는 단축키 command
+ N
을 통해 쉽게 만들 수 있다.package Hello.HelloSpring.Repository;
import Hello.HelloSpring.Domain.Member;
import java.util.Optional;
import java.util.List;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
package Hello.HelloSpring.Repository;
import Hello.HelloSpring.Domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long,Member> store = new HashMap<>();
private static long sequence = 0L; // 키 값 생성기
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member->member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<Member>(store.values());
}
public void clearStore(){
store.clear();
}
}
HashMap
자료구조를 통해 메모리에 회원들을 저장하였으며, static
변수를 선언함으로써 생성자 필요없이 저장소에 클래스 레벨로 접근할 수 있도록 하였다.save()
함수에서는 단순히 멤버를 저장소에 추가하는 함수로 구현하였다. (서비스 레벨이 아닌, 단순히 저장소에 멤버를 추가하는 것)findById()
, findByName()
함수는 각각 id와, name을 통해 회원을 찾는 함수이다. 이때 존재하지 않는다면 null이 나올 수 있기때문에 Optional
로 한 번 감싼다.findAll()
함수는 store에 저장된 멤버들의 값
들을 ArrayList
형식으로 리턴한다.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()
{
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
System.out.println("result = " + (result==member));
//Assertions.assertEquals(member,result);
assertThat(member).isEqualTo(result);
}
@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(member1.getName()).get();
assertThat(member1).isEqualTo(result);
}
@Test
public void findAll()
{
Member member1 = new Member();
member1.setName("Spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("Spring2");
repository.save(member2);
Member member3 = new Member();
member3.setName("Spring3");
repository.save(member3);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo( 3);
}
}
작성한 함수를 테스트하는데 있어서 main 메소드를 통해 실행해 확인해보거나, 웹 애플리케이션 컨트롤러를 통해 확인하면, 시간이 오래걸리고 생산성이 떨어진다. 자바의 프레임워크중 JUnit이라는 테스트를 실행해서 이러한 문제를 해결한다.
afterEach
함수는 테스트 파일내의 하나의 함수가 종료될때마다 실행되는 함수로, 각 테스트 함수들의 독립성을 보장하기 위해서 사용한다. Assertions.assertThat().isEqualTO()
함수를 이용한다.option
+ Enter
를 통해 할 수 있다.given
, when
, then
세 파트를 통해 정의한다. (주석을 통해 표현)package Hello.HelloSpring.Service;
import Hello.HelloSpring.Domain.Member;
import Hello.HelloSpring.Repository.MemberRepository;
import Hello.HelloSpring.Repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
} // MemberService와 MemberServiceTest에서의 같은 인스턴스를 사용하기 위해. DI(Dependency Injection)
public Long join(Member member)
{
validateDuplicateMember(member); //ctrl + t
// 회원 중복 검사
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long id) {
return memberRepository.findById(id);
}
}
주로 서비스레벨에서의 기능을 구현한다. 단순히 회원 저장소에 회원을 넣는 것에다, 중복id를 허용하지 않는 기능을 추가하며, 모든 회원 검색, 회원 한명 검색등의 기능 또한 서비스 레벨에서 구현한다.
회원 중복 검사시에 함수를 따로 빼내고 있다. 해당 단축키는 ctrl
+ t
로 가능하며, 중복 회원이 존재시에 IllegalStateException
메시지를 던진다.
테스트 코드는 원래 코드에서 단축키 ctrl
+ t
를 통해 접근 할수 있다.
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.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest { // command + shift + t
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach()
{
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository); // 의존성 주입. 엄연히 다른 인스턴스이므로...
}
@AfterEach
void afterEach()
{
memberRepository.clearStore();
}
@Test
void 회원가입() { // 빌드에 포함되지 않기때문.
//given
Member member = new Member();
member.setName("hello");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
Assertions.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));
//command + option + v
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
/*
try {
memberService.join(member2);
fail();
}catch (IllegalStateException e)
{
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.123123");
}*/
//then
}
@Test
void findMembers() {
//given
Member member = new Member();
member.setName("spring1");
memberService.join(member);
Member member2 = new Member();
member2.setName("spring2");
memberService.join(member2);
Member member3 = new Member();
member3.setName("spring3");
memberService.join(member3);
//then
List<Member> findMemberList = memberService.findMembers();
Assertions.assertThat(findMemberList.size()).isEqualTo(3);
}
@Test
void findOne() {
//given
Member member = new Member();
memberService.join(member);
//when
Member findMember = memberService.findOne(member.getId()).get();
Assertions.assertThat(findMember).isEqualTo(member);
}
}
해당 테스트 코드에는 의존성 주입 DI
의 개념이 들어가 있다.
왜 이 테스트 코드에는 DI가 필요할까?
잘 생각해보자. 테스트 코드에 회원이 저장되는 저장소와, 실제 Service 코드에서 회원이 저장되는 저장소가 같은가?
`public class MemberService {
private final MemberRepository memberRepository =
new MemoryMemberRepository();
TEST 코드에서 MemberService에 속한 기능(함수)들을 검증하기 위해서는 반드시 기본 생성자를 통해 생성해야 한다. 또한 TEST 코드에서 MemberRepository 또한 따로 생성해야 했다. 즉 이는, 서로 다른 객체이다. 따라서 이를 위해 다음과 같이 코드를 작성한다.
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
} // MemberService와 MemberServiceTest에서의 같은 인스턴스를 사용하기 위해. DI(Dependency Injection)
MemberService 객체를 생성할 때, 반드시 repository를 인자로 전달해 객체를 생성하도록 하고 있다. 따라서 TEST 시에 동일한 저장소
를 이용할 수 있게 된다.
인텔리제이 단축키
1. Command + N / ctrl + ENTER : getter, setter 함수 생성, 테스트 함수 생성
2. ctrl + T + 메소드 추출 : 함수 추출 후 private 함수로 분리
3. option + ENTER : 패키지 자동 추가(제네릭 등)