Spring/스프링 입문

회원 관리 예제 - 백엔드 개발 (1)

코징 2022. 3. 7. 13:06

 

비즈니스 요구사항 정리

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

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

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

 

1. Repository interface 구현

  • 현재 비지니스 로직에서 데이터 저장소가 선정되지 않았다고 가정하기 때문에, 인터페이스로 구현 차후에 선정될 시 변경함.
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(); // 전체 조회
}

 

2. Member 도메인 구현

  • id는 자동으로 추가되게 long으로 설정
package hello.hellospring.domain;

public class Member {

    Long id;
    String name;

    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;
    }
}

 

3. MemoryMemberReposoitory 인터페이스 구현체 구현

  • 저장소는 모든 클래스에서 데이터를 사용함으로 static으로 구현하였고, 맵을 통해 key값으로 출력될 수 있게 설계
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 seq = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++seq);
        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();
    }
}

 

4. Junit 테스트 구현

  • 도메인과 레퍼 지토리를 설계를 했으면 간단한 테스트가 가능하다.
  • test> java> hello.hellospring> repository> MemoryMemberRepositoryTest를 만든다.
package hello.hellospring.repository;

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

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

public 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);

        assertThat(repository.findById(member.getId()).get()).isEqualTo(member);
    }

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

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

        assertThat(repository.findByName("spring1").get()).isEqualTo(member1);
    }

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

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

        assertThat(repository.findById(member1.getId()).get()).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);

        assertThat(repository.findAll().size()).isEqualTo(2);
    }

}

모든 테스트는 순서와 상관없이 함수 별로 따로 동작하게 해야 된다.

때문에 afterEach 함수를 통해서 각 함수가 실행될 때마다 MemoryMemberRepository를 클리어로 초기화해줘야 된다.

  • @afterEach :  각 함수가 끝나는 시점에 항상 실행시켜줌
  • @Test : junit 언노테이션으로 해당하는 함수가 테스트 메서드이며 각자 실행시켜 준다는 것을 명시
  • assertThat : 기대한 값과 실제 값이 맞는지 확인하는 함수이다. 

이를 통해서 우리는 순서와 상관없이 각각의 함수들을 테스트하고 잘 돌아가는지 테스트를 할 수 있다.

물론 클래스 전체를 동작시킬 수 도 있다.

결론

 나는 개인적으로 다른 것보다 테스트하는 클래스를 만들고 각각의 함수 별로 테스트 하는 것을 처음 접해보았다. 항상 개발을 하다 보면 테스트보다는 동작을 우선시하게 되는데, 이러한 기능이 있다면, 내가 생각지 못한 예외들에 대해서 좀 더 유연하게 대처할 수 있을 것 같고, 이런 간단한 테스트 말고 TDD도 공부해야겠다는 생각이 들었다. 

 

이 글은 인프런의

제목 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

강사 : 김영한 님의 동영상을 참조해 만들었습니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard