본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[2025 1학기 스프링부트 스터디] 정다은 #3주차

반응형

 

이번강의는

🚀 섹션 4. 회원관리 예제 - 백엔드 개발🚀

입니다!

 

1. 🛸 비즈니스 요구사항 정리

  • 데이터 : 회원 ID, 이름
  • 기능 : 회원 등록, 조회
  • 아직 데이터 저장소가 선정 X (라는 가상 시나리오 설정)

아주 단순한 기능만 구현 예정입니다. 

 

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

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

클래스 의존관계

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

이제 진짜 개발을 하러 가보겟슴니다.

 

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

도메인 객체를 생성해줬습니다. 

id 식별자(시스템이 식별하는 id)와 name 생성이 필요합니다. 

 

회원객체 저장하는 저장소 repository를 생성해줍시다. 

(domain 안에 생성하는것 아님)

이렇게 인터페이스랑 클래스(구현체)도 생성해줬어요.

 

 회원 저장하고, id로 찾고, name으로 찾게 만들어봅시다.

기존 방식대로 null을 그대로 반환하는 대신 Optional로 묶어주는 거라고 해요. 

 

지금부터는 저장소에서 findById 나 findByName 으로 찾아올 수 있습니다.

findByAll은 지금까지 저장된 모든 회원을 반환해줍니다. 

 

이제 구현체를 만들어요! = 클래스 생성하겠다는 뜻.

 

제가 강의 듣고 실습 다 한 다음에 블로깅 하는거라서 이 화면 캡처를 미리 안 해둔 관계로 강의 화면 가져왔습니다.

단축키 써서 바로 메소드 생성해주면 됩니당 

 

hashMap 사용이나 long 등등 실무랑 조금 다른 점이 있지만 단순하게 간다고 하시네요.

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;

    @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<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

이렇게 구현해줬습니다.

요즘은 Optional.ofNullable() <- 이렇게 감싸서 보내준다고합니다.

 

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

작성한 리포지토리가 잘 작동하는지 테스트 해봅시다. 

자바 main 메서드 이용해서 실행 or 웹 어플리케이션 컨트롤러 이용해서 실행

-> 이 경우 준비+실행 오래걸림. 반복 실행 어려움. 여러 테스트 한번에 실행 어려움

 

JUnit 프레임워크<= 이용해서 문제 해결

이렇게 패키지와 클래스를 만들어줍니다

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.assertThat;

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();
        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("spring1").get();
        assertThat(result).isEqualTo(member1);
    }

    @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();

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

구현해준 모습입니다 ~ 

실행시키면 각각의 테스트 결과를 한 번에 확인 가능합니다.

이렇게 비교해줘도 되지만 이럼 글자를 매번 확인해야 합니다.

그런데 그럴순 없기 때문에 다른 방법을 쓸거에요

이렇게 Assertions 를 사용하면 출력되는 값 없이 테스트가 통과인지 아닌지 확인 가능합니당

Assertions 중에서도 junit 이 있고 assertj 가 있는데,

assertj를 이용하는게 조금 더 편해서 그걸 사용합니다! 

-> AssertThat 사용가능!

findByName도 테스트 시켜줬슴다

 

@AfterEach 이거 넣어준 이유는 

하나의 테스트가 끝날 때 마다 데이터를 비워줘야

다른 테스트에서 오류가 나지 않습니다~

4. 🛸 회원 서비스 개발

서비스 패키지 만들고 클래스 까지 생성해줍니당

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.assertThat;

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();
        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("spring1").get();
        assertThat(result).isEqualTo(member1);
    }

    @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();

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

 

 

 

 

 

 

4. 🛸 회원 서비스 테스트

package hello.hello_spring.service;

import hello.hello_spring.domain.Member;
import hello.hello_spring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    @AfterEach
    public 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();
        org.assertj.core.api.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));

        assertThat(e.getMessage()).isEqualTo("이미존재하는회원");
        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

테스트 코드에서는 한글 사용이 가능하답니다.

 

테스트할 때 given when then 이렇게 세 부분으로 나누어서

주석을 달아준 다음 코드를 작성하면 좋다고 합니다. 

 

중복_회원_예외 << 이 메서드를 이용해서 예외 로직 작동을 확인할 수 있습니다. 

try catch문 사용 << assertThrows 사용

 

🚀🛸  🛸🚀

어려웠어요 그렇지만 화이팅

반응형