일반적인 웹 어플리케이션 계층 구조
클래스 의존관계
domain/Member
//회원 도메인 객체
package hello.hellospring.domain;
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@Column(name = "username")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
repository/MemberReposotpry
//회원 리포지토리 인터페이스
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
//멤버 저장
Member save(Member member);
//아이디 찾기
Optional<Member> findById(Long id);
//이름 찾기
Optional<Member> findByName(String name);
//모두 찾기
List<Member> findAll();
}
repository/MemoryMemberRepository
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository{
//메모리 저장용 map
private static Map<Long, Member> store = new HashMap<>();
//ID값 생성을 위한 sequence
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)); //DB에서 id로 정보 조회 null이 있을때 반환하기 위해서 optional 사용
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream().filter(member -> member.getName().equals(name)).findAny(); //store의 값들을 쭉 보면서 이름과 파라미터로 받은 name이 일치하면 리턴
//findAny()는 가장 먼저 일치하는 요소 1개를 리턴해준다. findFirst()도 일치하는 요소 1개를 순서를 고려하여리턴해준다.
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
//저장소 초기화
public void clearStore(){
store.clear();
}
}
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다. 이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다. 자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.
src/test/java/MemoryMemberRepositoryTest
//회원 리포지토리 테스트케이스
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach // dao에 저장된 데이터를 삭제한다.
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
//given
Member member = new Member();
member.setName("spring");
//when
repository.save(member);
//then
Member result = repository.findById(member.getId()).get();
//org.assertj.core.api
assertThat(member).isEqualTo(result); //assertThat에서 member가 result랑 똑같다.
}
@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);
}
@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);
}
}
정상적으로 실행 됐을 때 결과창
메소드 별로 따로 동작하게 설계해야한다.
테스트 주도 개발 : 테스트 클래스를 먼저 작성한 뒤 구현 클래스를 작성할 수도 있다.(틀을 미리 작성하는 것)
given - when - then : 뭔가가 주어지면서(데이터) 이걸 실행했을때(검증 해야할 것) 결과가 이게 나와야 한다.(검증하는 부분)
@AfterEach : 하나의 메소드의 동작이 끝날 때마다 실행하도록 하는 메소드이다.
Assert는 프로그램에 대한 가정을 테스트 할수 있는 것으로, 프로그램의 오류를 감지하고 수정하는 효과적인 방법을 제공한다.
@Test를 메서드 위에 붙여 해당 메서드를 테스트 대상으로 지정할 수 있다. 그러면 그 메서드를 실행시킬 수 있는 상태가 된다.
org.junit.jupiter.api 를 import해야 실행할 수 있다.
service/MemberService
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
//회원가입
public Long join(Member member) {
validateDuplicateMember(member); //중복 회원 검증
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 memberId) {
return memberRepository.findById(memberId);
}
}
src/test/java/MemoryMemberRepositoryTest
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
@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());
}
@Test
public void 중복_회원_예외() throws Exception {
//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("이미 존재하는 회원입니다.");
}
}