스프링 입문 세션 4

Tamszero·2025년 5월 15일

ECC

목록 보기
7/17

비즈니스 요구사항 정리

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

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

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 도메인 객체를 가지고 핵심 비지니스 로직 구현 (ex. 회원은 중복 가입이 안됨)
  • 리포지토리: DB에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체(회원, 주문, 쿠폰 등등 주로 DB에 저장되고 관리됨)

클래스 의존관계

  • DB저장소가 선정되지 않았기 때문에 나중에 변경할 수 있도록 일단 인터페이스로 구현함
  • DB저장소는 RDB, NoSQL 등등 고민중이라고 가정
  • 개발을 진행하기 위해 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

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

  • hellospring에 domain 패키지 생성 후 Member 클래스 생성
package hello.hello_spring.domain;

public class Member {

    private Long id;
    private String name;

    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;
    }
}
  • hellospring에 리포지토리 패키지 생성 후 Member Repository 인스턴스 클래스 작성
package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;

import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member); //회원 저장하기
    Optional<Member> findById(Long id); //ID로 회원찾기
    Optional<Member> findByName(String name); //name으로 회원찾기
    List<Member> findAll();
}
  • 작성한 인스턴스를 활용해 MemoryMemberRepository 클래스 작성
    - 인스턴스 메서드 가져오기 -> alt+ enter
package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {


    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L; //키값을 0,1,2...이렇게 생성해줌


    @Override
    public Member save(Member member) {
        member.setId(++sequence); //member를 save할때 시퀀스 값을 1씩 올리며 돌려줌
        store.put(member.getId(), member);
        return member;
    }
    //ID를 셋팅 -> 스토어에 저장 (name은 그냥 회원이 입력하는 정보임)
    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id)); //null이 반환될 가능성이 있을때 옵셔널 널로 감싸주면 됨
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
                //루프를 돌면서 파라미터로 넘어온 name이랑 getName이랑 같은지 확인, 없으면 null을 반환
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
}

회원 리포지토리 테스트 케이스 작성

만든 클래스가 정상적으로 작동하는지 코드를 코드로 검증하는 방법
-> Junit을 사용

  • Test폴더에 MemoryMemberRepositoryTest 클래스 생성

저장한 값이랑 DB에서 꺼낸거랑 같은지 검증하기

@Test
    public void save(){

        Member member = new Member();
        member.setName("spring");

        repository.save(member); //리포지토리에 저장 -> ID가 세팅됨

        Member result = repository.findById(member.getId()).get(); //세팅된 아이디를 get을 통해 꺼내서 확인
        System.out.println("result = " + (result == member)); 저장한 거랑 DB에서 꺼낸거랑 같은지 검증하기 -> 글자로 출력
        

Assertions를 사용하여 기대값 확인하기


-> null이 아니므로 실행 결과 오류

findByName 확인하기

@Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        // member1,2 멤버회원이 가입이 됨

        Member result = repository.findByName("spring1").get(); //name이 spring1인 멤버 찾고 get으로 꺼내기

        Assertions.assertThat(result).isEqualTo(member1); //result가 member1이냐?
    }
  • 실행결과

    -> member2로 바꾸면 오류

findAll 테스트하기

 @Test
    public void findAll(){
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        Assertions.assertThat(result.size()).isEqualTo(2);

    }
    
-> 기대값 2 이므로 테스트 통과

전체 클래스를 한번에 테스트 돌리기

이때 주의할 점은 멤버가 중복 저장되므로 오류가 날 수 있음

  • MemoryMemberRepository에 clearStore메서드 추가
  • test 패키지 클래스에 afterEach 메서드 추가

-> 한번 테스트 메서드가 하나씩 끝날때마다 afterEach의 clearStore()을 호출하며 repository를 지우게됨

회원 서비스 개발

  • 서비스 패키지 생성

회원가입

  • 서비스 패키지에 MemberService 클래스 생성

이때 optional부분을 예쁘게(?) 수정한 코드

중복회원검증 부분을 메서드로 추출하기

전체회원 조회

-> findAll, findByID 모두 리턴타입은 List<Member>였으므로 걍 바로 반환하면 됨

회원 서비스 테스트

  • 클래스 이름 선택 후 ctrl+shift+t -> 그 클래스에 대한 테스트 클래스 껍데기를 알아서 생성해줌

회원가입 테스트

중복회원 예외 테스트

  • 중복 회원 가입시키고 try catch로 잡아보기

    정상적으로 예외 발생

  • AssertThrows로 잡아보기

  • Test시 사용되는 변수를 원래 레퍼지토리의 static 변수를 사용하기 위해 코드 수정

    -> MemberService 수정

    -> MemberServiceTest 수정

0개의 댓글