[Spring] 웹 계층 구조, Test

2024. 12. 17. 00:30·Spring

웹 계층 구조


 

  • Controller: HTTP 요청 검증 후 Service 계층에 전달
  • Service: 비즈니스 로직 처리, 트랜잭션 관리
  • Repository(데이터베이스 통신 계층)
    • JPA 기반 Repository: SQL 작성없이 JPA가 자동처리 (@Entity와 동작)  //findById, findAll, save 메서드통해 데이터처리
    • DAO (Data Access Object): SQL을 직접 다뤄 DB와 매핑(MyBatis 또는 JDBC)
  • Domain(데이터 모델 정의 계층)
    • Entity: DB 테이블과 매핑되는 객체 (데이터 저장, 수정 등 가능, @Entity통해 DB 테이블과 매핑) -> 데이터베이스 저장용
    • DTO: 데이터 전송을 위한 객체 (클라이언트 API 응답, 보안 목적) -> 데이터 전송용

 

데이터 저장소 미선정시 초기 개발


인터페이스 통해 구현클래스 변경가능하도록 설계, 가벼운 메모리 기반 데이터 저장소 사용.

 

-java/hello.hello_spring/repository/MemberRepository

public interface MemberRepository {
    Member save(Member member);
    //null이 반환될 가능성이 있는 경우, Optional<>로 감싸면 클라이언트 측에서 안전하게 처리 가능
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}

-MemoryMemberRepository

@Repository
public class MemoryMemberRepository implements MemberRepository {
    //동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
    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(m -> m.getName().equals(name))
                .findAny();
    }
    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

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

 

데이터 저장소가 선정되면 구현 클래스만 변경

(ex. 인터페이스 (MemberRepository) 유지 새로운 JpaMemberRepository만들어 구현)

 

테스트 케이스


 기능 테스트 시 main 메서드나 컨트롤러 통해 실행 -> 오래걸리고  반복실행 어려움. => JUnit 프레임워크 사용

src/test/java 하위 폴더에 생성 or ⌘⇧T

 

-src/test/java/hello.hello_spring/repository/MemoryMemberRepositoryTest

package hello.hello_spring.repository;

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

import java.util.List;

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

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach //메서드 실행 끝날때마다 동작
    public void afterEach() {
        repository.clear(); //테스트끝나면 지워지게 만들어
    }

    @Test
    public void save() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        repository.save(member);

        //then
        Member result = repository.findById(member.getId()).get();//Optional.get() 테스트에선 ㅇㅋ
        //Assertions.assertThat(result).isEqualTo(member); //org.assertj.core.api
        assertThat(result).isEqualTo(member); //opt+enter
    }

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

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

        //when
        Member result = repository.findByName("spring1").get();

        //then
        assertThat(result).isEqualTo(member);
    }

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

        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(결과) 구조 나누면 좋음

Service


-MemberService

public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService (MemberRepository memberRepository) {/////
        this.memberRepository = memberRepository;
    }

    /**
     * 회원가입
     */
    public Long join(Member member) {
        //같은 이름 있는 중복 회원x (메서드추출)
        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);
    }

}

 

-MemberServiceTest

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach //각 테스트 실행 전 호출
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    @AfterEach //각 테스트 실행 후 호출
    public void clear() {
        memberRepository.clear();
    }


    @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 중복_회원_예외() {
        //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("이미 존재하는 회원입니다.");

    }

}

 

 

통합테스트


db연동 후 통합 테스트할때

@SpringBootTest //스프링 컨테이너와 테스트를 함께 실행
@Transactional //테스트 시작 전 트랜잭션 시작, 완료 후에 롤백-> DB데이터 남지않아 다음테스트 영향x
class MemberServiceIntegrationTest {
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
     
     ...
     
     //@BeforeEach@AfterEach 안해도됨

'Spring' 카테고리의 다른 글

[Spring] AOP(Aspect-Oriented Programming)  (0) 2025.01.22
[Spring] 스프링 DB 접근(JDBC,JdbcTemplate,JPA,스프링 데이터 JPA)  (0) 2025.01.12
[Spring] From 입력, 조회  (0) 2024.12.22
[Spring] 스프링 빈 등록과 의존관계  (0) 2024.12.19
[Spring] 클라이언트-서버 통신 방식  (0) 2024.12.10
'Spring' 카테고리의 다른 글
  • [Spring] 스프링 DB 접근(JDBC,JdbcTemplate,JPA,스프링 데이터 JPA)
  • [Spring] From 입력, 조회
  • [Spring] 스프링 빈 등록과 의존관계
  • [Spring] 클라이언트-서버 통신 방식
Naah
Naah
  • Naah
    blueprint
    Naah
  • 전체
    오늘
    어제
    • 분류 전체보기 (106)
      • Java (28)
      • Kotlin (0)
      • TypeScript (7)
      • React (22)
      • Next.js (1)
      • Spring (22)
      • JPA (12)
      • Spring Data JPA (6)
      • Querydsl (1)
      • Error (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
    • manage
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Naah
[Spring] 웹 계층 구조, Test
상단으로

티스토리툴바