
비즈니스 요구사항 정리
이번 스터디는 간단한 회원 관리 시스템을 개발해본다. 본격적인 개발을 시작하기 전에, 비즈니스 요구사항 및 웹 애플리케이션 구조와 클래스 의존관계에 대해 정의를 먼저 한다.
간단한 개발과정이므로 데이터와 기능은 이정도로만 간단하게 정의하고, 데이터 저장소는 추후 결정되는 방향으로 설정한다.

계층 구조는 이와 같다. 핵심 로직을 구현할 서비스, DB에 접근하고 도메인 객체를 DB에 저장 및 관리할 리포지토리가 존재한다.

클래스 의존관계는 이와 같다. 아직 데이터 저장소가 정해지지 않았기 때문에 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계한다. 인터페이스로 구현해두면 후에 이를 구현하는 클래스가 후에 결정될 데이터 저장소에 따라 유연하게 변경될 수 있으며, 이를 통해 코드의 유지보수성과 확장성이라는 객체 지향의 설계 원칙을 따를 수 있다.
회원 도메인 및 리포지토리 생성

위와 같이 hello.hello_Spring 패키지 안에 domain과 repository라는 패키지를 생성한다. domain은 애플리케이션에서 활용될 핵심 데이터 모델, 사용자의 정보(id와 이름)을 담는 Member 클래스를 이 패키지 하위에 생성한다. 그리고 repository라는 데이터 저장소와 관련된 일들을 수행하는 인터페이스들과 클래스가 포함된 패키지를 생성한다.
Member class

MemberRepository Interface

MemoryMemberRepository

회원 리포지토리 테스트 케이스
개발한 기능을 테스트 할 때 자바의 main 메서드나 controller를 통해 이 기능을 실행하는데, 이는 준비 및 실행에 오랜 시간이 걸리고, 반복 실행하기 어렵다는 단점이 존재한다. 따라서 자바는 JUnit이라는 프레임워크를 통해 테스트를 실행한다.

이와 같이 test 모듈 안에 repository라는 새 패키지를 생성해주고, 그 안에 MemoryMemberRepository 클래스를 테스트할 테스트 클래스를 하나 생성해준다.
package hello.hello_Spring.repository;
import hello.hello_Spring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
class MemoryMemberRepositoryTest { //class 레벨에서도 테스트 가능 -> 두 메서드 한 번에 검사
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.findBy(member.getId()).get(); // Optional에서 값 꺼낼 때 get 활용
Assertions.assertThat(member).isEqualTo(result); //assertions 활용해 두 변수 비교 가능
}
@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.findBy("spring1").get();
Assertions.assertThat(result).isEqualTo(member1); // member2가 되면 test가 에러 뜸
}
@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);
}
}
테스트 코드를 총정리하면 이와 같다.
여기서 중요한 부분은, @AfterEach라는, 클래스 전체를 한 번에 테스트 하여 모든 메서드를 다 테스트 할 때, 데이터들이 중복되지 않게 한 메서드의 테스트가 끝났을 때 데이터들을 클리어해주는 메서드를 각각 추가해주어야 에러가 발생하지 않는다는 점이다. 추가적으로 테스트할 클래스 안에도 해당 메서드를 추가해주어야 한다. 이는 아래와 같다.

각 메서드 안의 코드는 일반 클래스 작성하듯이 정의해주면 된다.
*테스트 주도 개발
위의 방식은 클래스를 먼저 작성하고 후에 테스트 코드를 작성하여 테스트를 실행하였지만, 이와 정반대로 테스트코드를 먼저 작성하고, 그 후에 테스트코드에 맞춰 클래스를 작성하는 방법도 있다. 이를 '테스트 주도 개발' 이라고 하며, 실제 개발에서 여러부분에 있어 높은 품질의 코드 및 개발 속도 증가 등의 장점이 있어 유용하게 쓰일 수 있다.
회원 서비스
회원 서비스는 service라는 새로운 패키지를 만들어 하위에 해당 클래스를 생성해준다. 작성한 코드는 다음과 같다.
package hello.hello_Spring.service;
import hello.hello_Spring.domain.Member;
import hello.hello_Spring.repository.MemberRepository;
import hello.hello_Spring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
//회원가입 메서드
public long join(Member member){
//중복 회원 검증
ValidateDuplicateMember(member); // Ctrl+T로 메서드 분리시키기
memberRepository.save(member);
return member.getId();
}
private void ValidateDuplicateMember(Member member) {
memberRepository.findBy(member.getName()) //Optional로 결과가 반환되므로 바로 ifPresent 사용
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
//전체 회원 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findBy(memberId);
}
}
크게 회원가입 메서드와 회원 조회 메서드로 구분할 수 있다.
먼저 회원가입 메서드에서는 중복 검사를 할 수 있는 메서드를 하나 구현하고, 그 외에는 회원의 정보와 아이디를 반환시켜주는 메서드를 추가한다. 전체 회원 조회는 리포지토리에 있는 메서드를 활용한다.
회원 서비스 테스트