뚜룽

[Spring] 인프런 스프링 입문 정리 (5) / 도메인, 리포지토리, 테스트케이스 작성 본문

카테고리 없음

[Spring] 인프런 스프링 입문 정리 (5) / 도메인, 리포지토리, 테스트케이스 작성

ddooroong 2023. 10. 22. 23:12

목차

  1. 프로젝트 구조
  2. 회원정보 도메인, 리포지토리 설계
  3. 테스트 케이스 작성

 

1. 프로젝트 구조

 

이번에는 강의 섹션3의 비즈니스 요구사항 정리, 회원 도메인과 리포지토리 만들기, 회원 리포지토리 테스트 케이스 작성 이 3개의 강의를 정리하였다. 글은 도메인(domain) 패키지, 리포지토리(repository) 패키지, 테스트 케이스 순서로 정리하였다. 

프로젝트 구조

이번 글의 프로젝트 구조는 위의 사진과 같이 이루어져 있다. 도메인 패키지에서는 회원 정보를 정의하고, 회원 정보에 접근하거나 조회하는 로직은 리포지토리 패키지에 정의되어 있다. 그리고 리포지토리 패키지에서 회원 정보를 등록하고 조회하는 기능이 잘 수행되는지 테스트하기 위한 코드는 'test' 패키지에 작성한다. 

 

2. 회원 정보 도메인, 리포지토리 설계

 

이 강의에서는 별도의 DB를 사용하지 않고 메모리 기반의 데이터 저장소를 사용한다. 그 말은 즉슨 데이터 베이스에 insert, select를 사용하지 않고 java의 자료형에 대입하여 그 메모리를 사용한다는 의미이다. 여기서는 먼저 아래와 같은 순서로 작업을 하였다. 

2.1 회원 정보 도메인 

  1. src/main/java/hello.hellospring 디렉토리 하위에 "domain" 패키지 생성
  2. domain 패키지에 "Member" 클래스 생성

domain 패키지 생성

회원 정보는 회원 ID와 이름으로만 저장한다. 이때 회원 ID는 시스템에서 long 타입 변수로 시스템에서 정의한다. 두 개의 변수(회원 ID, 이름), 그리고 Getter, Setter를 아래와 같이 만들어 준다. 

  • Member.java
    package hello.hellospring.domain;
    
    public class Member {
    	
        private Long id;
        private String name;
        
        public long getId() {
        	return id;
        }
        
        public void setId(Long id) {
        	this.id = id;
        }
        
        public String getName() {
        	return name;
        }
        
        public void setName(String name) {
        	this.name = name;
        }
    }​

Member 클래스에서는 회원의 아이디와 이름을 읽어오거나 아이디, 이름에 값을 저장하는 업무만 담당한다.


2.2 회원 정보 리포지토리

다음으로 회원 리포지토리를 만들어보자. 리포지토리에서는 앞서 만든 Member 클래스를 사용해서 실제로 회원의 정보를 등록하고, 아이디나 이름으로 회원을 조회하거나 모든 회원 정보를 조회하는 코드를 작성한다. 즉, Member 클래스에서는 회원 정보로 정의한 아이디, 이름이라는 요소의 값을 정의하는 곳이라면, 리포지토리에서는 그렇게 등록된 Member 객체의 정보를 저장하거나 읽어오는 역할을 담당하고 있다.  

리포지토리에서는 인터페이스와 구현체를 하나씩 만들게 된다. 인터페이스에서는 각 메소드를 선언하고, 실제 구현체에서 이 메소드의 내용을 채워주면 된다. 총 4개의 메소드를 만들어 보자. 

  • MemberRepository: 회원 리포지토리의 인터페이스, 메소드 선언부가 이 곳에 정의됨
  • MemberMemoryRepository: 회원 리포지토리(MemberRepository)의 구현체
    • public save(Member member) :  회원 객체(id, 이름)를 저장함
    • Optional<Member> findById(Long id) : id값으로 회원의 정보를 가져옴
    • Optional<Member> findByName(String name) : 이름으로  회원의 정보를 가져옴
    • List<Member> findAll() : 모든 회원의 정보를 가져옴

 

  • MemberRepository.java (I)
    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();
    }​
  • MemberMemoryRepository.java (C)
    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    
    import java.util.*;
    
    public class MemoryMemberRepository implements MemberRepository{
    
    // 동시성 문제는 고려하지 않은 코드
    // 실무에서는 ConcurrentHashMap, AtomicLong를 주로 사용함
    
        private static Map<Long, Member> store = new HashMap<>();
        private static long sequence = 0L;
    
    	// 1. 회원 정보를 저장하는 코드
        //- Member 객체에 Id값을 저장함과 동시에 회원 정보를 저장한다. 
        @Override
        public Member save(Member member) {
            member.setId(++sequence);
            store.put(member.getId(), member);
            return member;
        }
    
    	//2. ID로 회원 정보를 조회하는 코드
        //- HashMap에서 id를 키값으로 갖는 value를 찾고, 없을 경우 Optional로 null 처리 하여 반환
        @Override
        public Optional<Member> findById(Long id) {
            return Optional.ofNullable(store.get(id));
        }
    
    	//3. 이름으로 회원 정보를 조회하는 코드
        //- HashMap에서 이름을 키값으로 갖는 value를 찾고, 없을 경우 Optioanl로 null 처리 하여 반환
        @Override
        public Optional<Member> findByName(String name) {
            return store.values().stream()
                    .filter(member -> member.getName().equals(name))
                    .findAny();
        }
    
    	//4. 모든 회원의 정보를 조회하는 코드
        //- HashMap의 모든 값을 ArrayList로 반환
        @Override
        public List<Member> findAll() {
            return new ArrayList<>(store.values());
        }
    
    	//5. 메모리에 저장된 모든 회원 정보를 삭제 (테스트 케이스에서 필요)
        public void clearStore() {
            store.clear();
        }
    }

이렇게 Member Respository와 구현체를 만들었다. 회원의 정보가 잘 등록되는지, 회원 정보를 이름이나 아이디로 검색했을 때 조회가 잘 되는지를 알아보려면 우리는 테스트 케이스를 작성해서 검사를 해보는 것이 좋다. 

 

3. 테스트 케이스 작성

 

테스트 케이스는 말 그대로 각각의 기능이 의도에 맞게 오류없이 동작하는지 확인하기 위한 코드를 작성하는 것이다. 

3.1 Junit

 Junit은 Java 기반 코드를 독립된 단위로 테스트 할 수 있도록 지원해주는 프레임워크이다. @Test 어노테이션이 붙은 메소드 단위별로 테스트를 독립적으로 진행한다. Junit에서는 Assert라는 클래스를 가지고 기댓값과 실제값이 같은지를 비교하여 테스트를 한다. 


3.2 Junit 테스트 어노테이션

강의에서 테스트 케이스 코드 중에 @AfterEach 어노테이션에 대해서 설명해주셨다. 그래서 Junit의 @Test 어노테이션과 @AfterEach와 비슷한 역할을 하는 어노테이션 몇 가지를 정리해보았다. 

어노테이션 역할
@Test 이 메소드가 테스트 메소드임을 나타낸다. 
@BeforeEach 각각의 테스트 메소드를 시작하기 전에 실행되어야 하는 메소드임을 나타낸다.
@AfterEach 각각의 테스트 메소드가 끝난 후에 실행되어야 하는 메소드임을 나타낸다. 
@BeforeAll 모든 테스트 메소드를 시작하기 전에 실행되어야 하는 메소드임을 나타낸다. 
@BeforeAll 어노테이션이 붙은 메소드는 반드시 Static 메소드로 작성되어야 한다.
@AfterAll 모든 테스트 메소드가 끝난 후에 실행되어야 하는 메소드임을 나타낸다. 
@AfterAll 어노테이션이 붙은 메소드는 반드시 Static 메소드로 작성되어야 한다. 

만약 위의 모든 어노테이션이 사용되었다면, 각 어노테이션은 이런 순서로 실행될 것이다. @Test 어노테이션이 붙은 코드가 여러개라면 파란 글씨의 과정이 계속 반복될 것!

@BeforeAll - @BeforeEach - @Test - @AfterEach - @AfterAll


3.3 테스트 케이스 작성, 실행

테스트 케이스는 내가 실행할 클래스의 이름 뒤에 'test'를 붙여서 클래스를 하나 만든다. 그리고 메소드의 로직에 맞게 코드를 작성한다. 

  • MemberMemoryRepositoryTest.java (C)
    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.Test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    class MemoryMemberRepositoryTest {
    
        MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();
    
        @AfterEach
        public void afterEach() {
            memoryMemberRepository.clearStore();
        }
    
        @Test
        public void save() {
            Member member = new Member();
            member.setName("spring");
    
            memoryMemberRepository.save(member);
    
            Member result = memoryMemberRepository.findById(member.getId()).get();
    //        System.out.println("result = " + (result == member));
    //        Assertions.assertEquals(member, result);
            Assertions.assertThat(member).isEqualTo(result);
        }
    
        @Test
        public void findByName() {
            Member member1 = new Member();
            member1.setName("spring1");
            memoryMemberRepository.save(member1);
    
            Member member2 = new Member();
            member2.setName("spring2");
            memoryMemberRepository.save(member2);
    
            Member result = memoryMemberRepository.findByName("spring1").get();
    
            Assertions.assertThat(result).isEqualTo(member1);
    
        }
    
        @Test
        public void findAll() {
            Member member1 = new Member();
            member1.setName("spring1");
            memoryMemberRepository.save(member1);
    
            Member member2 = new Member();
            member2.setName("spring2");
            memoryMemberRepository.save(member2);
    
            List<Member> result = memoryMemberRepository.findAll();
    
            Assertions.assertThat(result.size()).isEqualTo(2);
        }
    
    }​

테스트 코드에서 실행한 결과가 실제로 그 메소드의 로직의 기댓값과 같은지 비교하는 방식으로 테스트를 한다. 그 문장은 이렇게 작성하면 된다. 

Assertions.assertThat(결과값).isEqualTo(기댓값);

예를 들어, 테스트 메소드의 결과값이 5이고, 메소드의 실제 결과로 나와야만 하는 값이 10이라면 이렇게 작성한다. 

Assertions.assertThat(5).isEqualTo(10);

프로젝트를 할 때 테스트 코드는 반드시 작성하는 것이 좋다. 기능을 단위별로 테스트 하면서 오류를 쉽게 찾을 수 있기 때문이다. (학원 다닐 때 나는 테스트 코드만 작성하면 에러가 났던 기억이.. 내가 뭔가 잘못 했겠지 ^^)


참고

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo

junit.org

 

[Spring boot] 테스트 코드 작성 (1) - Junit을 이용한 Unit Test(단위 테스트) / Assert 메소드

Spring 테스트 코드 작성해보기! 목차 Junit 이란 Junit 사용설정 Junit 어노테이션 Junit 사용법 Assert 메소드 @Nest사용 엣지 케이스 확인 Junit을 이용한 단위테스트 1) 단위 테스트란 프로그램을 작은 단

thalals.tistory.com