728x90

아직 배운 것은 많이 없지만 그래도 예제(학생 관리 예제)를 하나 작성 해보자

 

소프트웨어설계에서 공부한 대로

 

비즈니스 요구사항 정리

학생 도메인과 레포지토리 만들기

학생 레포지토리 테스트 케이스 작성

학생 서비스 개발

 

의 순서로 진행이 될 예정이다.

 

  • 비즈니스 요구사항 정리

우선 비즈니스 요구사항을 정리하자면

 

학교라고 설정을 하고

 

데이터는 학번, 이름

기능은 학생 등록, 조회

이 정도만 구현을 해보도록 하자

 

일반적으로 구조는

컨트롤러는 저번에 만들어 보았던 대로 웹 MVC에서 C의 컨트롤러이다.

서비스는 비즈니스에 관련된 로직을 구현한다.

리포지토리는 데이터베이스에 접근하며, 객체를 DB에 저장하고 관리한다.

 

아직 리포지토리에서 어떤 저장소를 사용할지 결정하지 않아서 interface로 클래스를 변경할 수 있도록 설계한다.

  • 학생 도메인과 리포지토리 만들기

당연히 프로젝트를 생성하고

클래스를 작성할 패키지를 생성한 후 작성을 해준다.

package spring_practice.spring_practice.domain;

public class Member {
    
    private Long studentId; //학번
    private String name; //이름

    public Long getStudentId() {
        return studentId;
    }

    public void setStudentId(Long studentId) {
        this.studentId = studentId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

이렇게 학번과 이름을 가지고 있는 클래스를 만들고, command + n을 이용하여 getter와 setter를 작성해 준다.

 

이번엔 리포지토리를 만들자, 아직 선정되지 않았으니 인터페이스를 만들고 구현을 하도록 해야 한다.

package repository;

import 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(); //지금까지 저장된 학생을 LIST 로 받아오는 메서드
}

 

일단 이 인터페이스를 구현할 class를 작성한다.

package spring_practice.spring_practice.repository;

import spring_practice.spring_practice.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, Member> store = new HashMap<>(); //학생들의 정보를 저장할 Map
    private static Long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setStudentId(++sequence);
        store.put(member.getStudentId(), 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();
    }
}

학생들의 데이터는 일단 Map을 이용하여 저장을 한다.

 

  • 학생 리포지토리 테스트 케이스 작성

일단 여기까지 하고 바로바로 테스트를 진행해 보자.

test 케이스는 test 폴더 밑에 작성을 한다.

 

그리고 보통 테스트 케이스의 패키지는 기존 패키지와 똑같이 작성을 한다.

그리고 테스트할 class의 파일명에 Test를 붙여서 테스트 케이스의 이름을 작성한다.

 

간단하게 command + shift + T로 만들 수도 있다.

여기 안에 테스트 코드들을 작성해야 한다.

각 메서드들을 보면 @Test annotation이 달려있는 것을 볼 수 있다.

 

class 내에서 윗부분에

    MemoryMemberRepository repository = new MemoryMemberRepository();
    
    @AfterEach
    public void afterEach(){
        repository.clearStore();
    }

이 코드를 추가하자

repository는 테스트하면서 사용할 리퍼지토리이고 afterEach메서드는 각각의 메서드를 테스트할 때마다 리퍼지토리를 비워즈는 메서드이다.

@AfterEach annotation을 달아서 각 테스트 후에 실행이 되도록 해준다.

 

차례로 save 메서드부터 테스트해 보자

save 메서드는 새로운 학생을 추가하는 메서드이다.

    @Test
    void save() {
        //given
        Member member = new Member();
        member.setName("seungkyu");
        
        //when
        repository.save(member);
        
        //then
        Member result = repository.findById(member.getStudentId()).get();
        Assertions.assertEquals(member, result);
    }

테스트 케이스는 given, when, then 상황 3가지로 나누어 작성을 하고

seungkyu라는 이름을 가진 멤버를 추가하고 repository에서 학번으로 해당 멤버를 찾은 후 Assert로 둘이 일치하는지 확인하는 테스트이다.

 

이렇게 작성을 한 후 run을 하면

이렇게 문제없이 초록색으로 보이게 된다.

 

이번엔 이름을 이용해서 가져오는 findByName을 테스트하자

    @Test
    void findByName() {
        //given
        Member member1 = new Member();
        member1.setName("seungkyu1");
        repository.save(member1);

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

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

        //then
        Assertions.assertEquals(member1, result);
        Assertions.assertNotEquals(member2, result);
    }

이름을 이용해 가져와보고 member1과 result가 같은지, member2와 result가 같지 않은지를 확인해 본다.

테스트를 수행하면 정상적으로 초록불이 나올 것이다.

 

마지막으로 전체를 가져오는 findAll이다.

@Test
    void findAll() {
        //given
        Member member1 = new Member();
        member1.setName("seungkyu1");
        repository.save(member1);
        
        Member member2 = new Member();
        member2.setName("seungkyu2");
        repository.save(member2);
        
        //when
        List<Member> result = repository.findAll();
        
        //then
        Assertions.assertEquals(2, result.size());
    }

이렇게 작성을 하면 2개의 객체를 추가했으니 사이즈는 2가 되어야 한다.

AfterEach를 수행하고 테스트를 수행하기 때문에 정상적으로 초록불이 들어와야 한다.

 

  • 학생 서비스 개발

비즈니스에 가까운 서비스를 개발해 보자.

Service 패키지를 따로 만든 후

 

우선 memberRepository를 생성자를 이용해 주입할 수 있도록 만들고

package spring_practice.spring_practice.service;

import spring_practice.spring_practice.repository.MemberRepository;

public class MemberService {

    private final MemberRepository memberRepository;

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

이렇게 DI가 가능하도록 설계를 해야 나중에 리퍼지토리를 변경할 때 적은 코드를 수정할 수 있게 된다.

 

package spring_practice.spring_practice.service;

import spring_practice.spring_practice.domain.Member;
import spring_practice.spring_practice.repository.MemberRepository;

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

public class MemberService {

    private final MemberRepository memberRepository;

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

    private void validateDuplicateMember(Member member){ // 같은 이름의 학생이 있는지 찾아주는 메서드
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 등록된 학생입니다.");
                });
    }

    public Long join(Member member){
        validateDuplicateMember(member);
        memberRepository.save(member); // 이미 등록된 학생이 아니면 저장
        return member.getStudentId();
    }

    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findStudent(Long studentId){
        return memberRepository.findById(studentId);
    }
}

원래는 같은 이름을 가진 학생들이 있겠지만 학습을 위해 같은 이름의 학생을 등록할 수 없도록 만들어보자.

 

이해하기 어렵지 않을 것이고 바로 테스트 케이스를 만들어보자.

당연히 command + shift + T로 만들것이며 AfterEach에 이어서 테스트가 수행되기 전에 실행되는 BeforeEach를 사용할 것이다.

 

이 서비스는 생성자를 통해 외부에서 리퍼지토리를 주입해주어야 했다.

그렇기에 BeforeEach를 통해 각 테스트 실행 전마다 주입해 줄 예정이다.

package spring_practice.spring_practice.service;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import spring_practice.spring_practice.repository.MemoryMemberRepository;

import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

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

    @Test
    void findMembers() {
    }

    @Test
    void findStudent() {
    }
}

 

나머지 코드들을 작성해보자.

package spring_practice.spring_practice.service;

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 spring_practice.spring_practice.domain.Member;
import spring_practice.spring_practice.repository.MemoryMemberRepository;

import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

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

    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }

    @Test
    void join() throws Exception{
        //given
        Member member = new Member();
        member.setName("seungkyu");

        //when
        Long stuId = memberService.join(member);

        //then
        Member findMember = memberRepository.findById(stuId).get();
        Assertions.assertEquals(member, findMember);
    }

    void dupliExc() throws Exception{
        //given
        Member member1 = new Member();
        member1.setName("seungkyu");

        Member member2 = new Member();
        member2.setName("seungkyu");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        Assertions.assertEquals(e.getMessage(), "이미 등록된 학생입니다.");
    }
}

저렇게 seungkyu라는 이름의 학생이 두명이니 중복 예외가 발생해야 할 것이고 그것까지 테스트하는 코드이다.

'백엔드 > 스프링' 카테고리의 다른 글

스프링 7일차  (0) 2023.02.07
스프링 6일차  (0) 2023.02.06
스프링 5일차  (0) 2023.02.04
스프링 4일차  (0) 2023.02.03
스프링 3일차  (0) 2023.02.01

+ Recent posts