[Spring] 스프링 입문 (코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술) - 백엔드 개발

밀크야살빼자·2023년 4월 18일
0

비지니스 요구사항 정리

  • 데이터 : 회원ID, 이름
  • 기능 : 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않은 가상의 시나리오

일반적인 웹 어플리케이션 계층 구조

  • 컨트롤러 : 웹MVC의 컨트롤러 역할한다.
  • 서비스 : 핵심 비즈니스 로직 구현한다.
  • 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리한다.
  • 도메인 : 비즈니스 도메인 객체 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리한다.

클래스 의존관계

  • 아직 데이터 저장소가 선정되지 않아서, 인터페이스로 구현 클래스를 변경할 수 있도록 설계한다.
  • 개발을 진행하기 위해서 초기 개발단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용한다.

회원 도메인과 리포지토리 만들기

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("이미 존재하는 회원입니다.");
     }
}
  • @Before : 각 테스트 실행전에 호출된다. 테스트가 서로 영향이 없도록하고 항상 새로운 객체를 생성한다.
profile
기록기록기록기록기록

0개의 댓글