비즈니스 요구사항 정리
스프링 구조를 이해하기 위한 단순한 예제이기 때문에 복잡한 기능을 사용하지 않을 예정이다.
- 데이터 : 회원ID, 이름
- 기능 : 회원 등록, 조회
- DB는 정해지지 않았다고 가정
일반적인 웹 애플리케이션 계층 구조
- 컨트롤러 : 웹MVC의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현 (예. 회원 중복 가입 제한 등)
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체, 주로 데이터베이스에 저장하고 관리됨 (예. 회원, 주문, 쿠폰 등)
클래스 의존 관계
DB 저장소가 선정되지 않았기 때문에 초기 개발 단계에서는 구현체로 메모리 기반의 데이터 저장소를 사용한다.
추후에 어떤 기술을 사용할지(예. jpa..), 어떤 DB를 사용할지 결정되면 바꿔 끼울 수 있어야하기 때문에 인터페이스를 사용하여 구현 클래스를 변경할 수 있도록 설계한다.
회원 도메인과 리포지토리 만들기
- Member 클래스를 생성 / id, name 변수 생성 / getter,setter 생성 (인텔리제이 단축키 : Alt + Insert(Window))
- MemberRepository 인터페이스 생성
- 저장 기능
- id, name으로 찾기 기능
- 전체 조회 기능
- MemoryMemberRepository 구현체 생성
- MemberRepository 를
implements
하여 각 함수 구현
- MemberRepository 를
회원 리포지토리 테스트 케이스 작성
자바의 main 메소드나 웹 어플리케이션의 컨트롤러를 통해 기능을 실행하여 테스트하는 방법
- 준비/실행하는데 시간이 오래걸림
- 반복 실행이 어려움
- 여러 테스트를 한번에 실행하기 어려움
자바는 JUnit이라는 프레임워크로 테스트를 실행하여 이러한 문제를 해결한다. 테스트케이스는 @Test 어노테이션을 사용한다.
src/test/java/
하위에 실행할 repository이름+Test 로 명명하여 테스트 케이스 만드는 것이 관례이며, 함수 단위/클래스 단위로 테스트 케이스 실행이 가능하다.
System.out.println으로 확인할 수도 있지만 Assertions 을 더 많이 사용하는데 두가지 방법이 있다.
assertEquals(member, result); //org.junit.jupiter.api
assertThat(member).isEqualTo(result); //org.assertj.core.api
static import를 사용하면 바로 함수 사용이 가능하다.
//직접 사용
Assertions.assertThat(member).isEqualTo(result);
//static import후 사용
import static org.assertj.core.api.Assertions.*;
assertThat(member).isEqualTo(result);
클래스 단위로 돌리게 되면 한번에 여러 테스트가 실행되는데, 메모리 DB에 직전 테스트의 결과가 남을 수 있다. 이전 테스트 때문에 다음 테스트가 실패할 가능성이 있기 때문에 각 테스트가 종료될 때마다 데이터를 클리어해주는 등 작업이 필요하다. 이 때 @AfterEach
어노테이션을 사용한다.
또한 실행 순서가 보장되지 않기 때문에 모든 테스트는 순서와 상관없이 메소드별로 따로 동작하도록 설계되어야한다.
회원 서비스 개발
리포지토리를 만들 때에는 단순하게 데이터를 넣었다 빼는 정도로 기능을 구현한다면, 서비스를 만들 때에는 중복 가입을 확인하여 예외 처리를 하는 등 비즈니스 로직에 집중하여 구현해야한다. 네이밍도 비즈니스에 의존적으로 설계한다.
회원 서비스 테스트
테스트 함수명은 기존 서비스의 함수명인 'join' 대신 '회원가입' 과 같은 한글로 작성해도 무방하다. 빌드될 때 포함되지 않을 뿐더러 영어권 사람들과 같이 작업하는게 아닌 이상 빠르고 직관적인 작업이 가능하다.
테스트 케이스를 작성할 때에는 given-when-then 문법으로 작성하여 어떤 값이 주어졌을 때 어떤 상황에서 어떤 결과가 나와야하는지 테스트 케이스를 작성하는 것이 바람직하다.
정상 결과를 내는 테스트보다 예외 처리될 때의 테스트가 더 중요하다. try-catch문으로 예외 처리 Exception을 받아서 처리할 수도 있지만 JUnit Assertion의 assertThrows 함수를 사용하는 것이 더 좋다.
@AfterEach가 각 메소드 실행 후 작업이라면 @BeforeEach
는 각 메소드 실행 전 작업이다. MemberService에서 직접 Repository를 생성하지 않고 생성자를 만들어두면, BeforeEach에서 테스트 함수 실행될 때마다 MemberService가 생성되어 외부에서 Repository를 넣어주도록 유도할 수 있다. (DI)
더 공부해보자
- Optional (Java8)
- 리턴 값이 null일 때 Optional로 감싸서 반환하는 방법을 선호 Optional.ofNullable()
- 동시성 문제
- ConcurrentHashMap vs. HashMap
- AtomicLong vs. Long
- Lambda (Java8)
- static import
이 포스팅은 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 수강하며 작성되었습니다.
[지금 무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 - 인프런
스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세
www.inflearn.com
'Spring Framework' 카테고리의 다른 글
[스프링 입문] 웹 MVC 개발 (0) | 2024.01.25 |
---|---|
[스프링 입문] 스프링 빈과 의존관계 (0) | 2024.01.22 |
[스프링 입문] 스프링 웹 개발 기초 (0) | 2024.01.19 |
[스프링 입문] 강의 소개 및 프로젝트 환경 설정 (0) | 2024.01.17 |
[예제로 배우는 스프링 입문] 스프링 PSA (0) | 2023.12.17 |