11. 회원 리포지토리 테스트 케이스 작성

test 폴더에 똑같은 repository 패키지를 만들고 MemoryMemberRepositoryTest 클래스를 만드는게 일반적이다.

hello.hellospring.repository의 MemoryMemberRepositoryTest 클래스

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest { // 얘는 굳이 public으로 안해도 된다. 다른데서 쓸게 아니니까

    MemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save() { // ctrl + shift + enter 누르면 끝까지 커서를 땡기고 엔터치지 않아도 바로                          
                         // 개행이 됨. (굉장히 편리함)
        Member member = new Member();
        member.setName("spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        // findById 반환타입이 Optional이므로 get으로 꺼내줘야함.
        // 사실 get으로 꺼내는게 좋은 방법은 아니지만 테스트코드같은데서는 get으로 꺼내도 된다.

        System.out.println("result = " + (result == member));
        // 이렇게 출력해서 볼 수도 있지만 이렇게 글자로 매번볼 수 없다. 그래서 ASSERT기능을 활용한다.
    }
}

👉 result

이렇게 출력해서 볼 수도 있지만 이렇게 글자로 매번볼 수 없다. 그래서 ASSERT기능을 활용한다.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

        Assertions.assertEquals(member, result); 
        // spring이름을 가진 멤버를 저장했다가                 
        // find로 다시 찾았을때 잘 튀어나와야 한다.
    }
}

👉 result

실제로 실행해보면 출력되는건 아무것도 없지만 초록색으로 체크되면서 정상 작동됨을 확인할 수 있다.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

        Assertions.assertEquals(member, null); 
        // 만약 이렇게 일부로 null 넣어서 실패하면 기대했던값과 실제값이 출력된다.
    }
}

👉result

이렇게 기대값과 실제값을 출력해주면서 어떤게 잘못됬는지 확인할 수 있다.


Assertions에도 org.junit.jupiter.api가 있고, org.assertj.core.api가 있는데,
위의 Assertions는 org.junit.jupiter.api를 쓴거고, org.assertj.core.api는 아래에 구현해보자.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

        Assertions.assertThat(member).isEqualTo(result); 
        // org.assertj.core.api 꺼다.
    }
}

👉result

잘됨.

여기서 Assertions는

static이므로 import하면

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*; // 추가됨

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

                    assertThat(member).isEqualTo(result); // 이렇게 Assertions 없이 바로 assertThat 쓰면됨
    }
}

마찬가지로 이거도 result 대신 null을 쓰면 빨간 불이 뜨면서 expecteced, actual이 뜬다.

실무에서는 이거를 빌드툴과 엮어서,

빌드툴을 가지고 빌드할때, 오류 발생하고 테스트 케이스가 통과하지 않으면 다음단계 못넘어가게 막아둠.


이번엔 findByName을 테스트 해보자

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

                    assertThat(member).isEqualTo(result); 
    }

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

            Member member2 = new Member(); 
            // 위에 3줄 복사해왔는데, member1을 2로 바꿀때 shift + F6 누르면 동시에 다 바뀜
            member2.setName("spring2"); // 물론 스트링안은 안바뀌니 직접 바꿔주자
            repository.save(member2);

            Member result = repository.findByName("spring1").get(); 
            // Optional 에서 꺼내주기 위해 get 사용

            assertThat(result).isEqualTo(member1);
        }
}

👉result

녹색불이 잘 뜬다.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

                    assertThat(member).isEqualTo(result); 
    }

    @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.findByName("spring2").get(); 
       // 만약 spring2로 바꿔서 찾는다면?

            assertThat(result).isEqualTo(member1);
        }
}

👉result

member1이랑 member2랑 다르니 빨간불 뜬다.

테스트 케이스의 좋은 장점은

이렇게 클래스를 돌리면 그안의 메서드를 다 테스트 해볼 수 있다.


이번엔 findAll을 검증해보자.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

class MemoryMemberRepositoryTest { 

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

                    assertThat(member).isEqualTo(result); 
    }

    @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.findByName("spring2").get(); 

       assertThat(result).isEqualTo(member1);
    }

    @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();

        assertThat(result.size()).isEqualTo(2); 
        // 멤버 2개를 만들었으니, findAll로 찾아도 size가 2가 나와야 한다.
    }
}

👉result

근데 아래와 같이 틀리게 쓴다면?

assertThat(result.size()).isEqualTo(3); // 만약 3으로 바꾸면?

👉result

에러가 나오게 된다.

다시 2로 돌려놓고 이제 테스트가 정상적으로 실행됬겠지? 라고생각하고 클래스 전체를 테스트 돌리면

갑자기 에러가 뜬다. 왜?

지금 자세히보면 테스트 순서가 findAll이고 그다음이 findByName이 실행됬다. 순서는 보장이안된다.

모든 테스트는 순서랑 상관없이 메서드별로 다 따로 동작하도록 설계를 해야 한다.

(순서 의존적으로 설계하면 절 때 안된다.)

순서상으로 봤을때 findAll을 하면서 객체에 이미 저장을 해놓고 그 상태에서 findByName을 하려니 문제가 생긴거다.

그래서 테스트가 끝나고 나면 데이터를 깔끔하게 클리어 해줘야한다.

@AfterEach는 메서드가 끝날때마다 이 동작을 취하게 되는 어노테이션이다. (콜백 메서드라고 생각하면 된다.)

// MemberRepository repository = new MemoryMemberRepository();
MemoryMemberRepository repository = new MemoryMemberRepository();

그리고 MemoryMemberRepository만 테스트하는거니까 일단 인터페이스말고 인터페이스를 구현한 MemoryMemberRepository 클래스로 바꿔주자.

[hello.hellospring.repository의 MemoryMemberRepository클래스]

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    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(member -> member.getName().equals(name))
                .findAny(); 
    }

    @Override
    public List<Member> findAll() { 
        return new ArrayList<>(store.values()); 
    }

    public void clearStore() {
        store.clear(); // Test 코드를 위해 작성, 데이터를 제거해줌.
    }
}

[hello.hellospring.repository의 MemoryMemberRepositoryTest 클래스]

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

class MemoryMemberRepositoryTest { 

    MemoryMemberRepository repository = new MemoryMemberRepository(); // 바꿈

    @AfterEach
    public void afterEach() {
        repository.clearStore(); 
        // 이걸 추가해서 테스트가 실행될때마다 리포지토리의 store를 지운다.
    }

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

                    assertThat(member).isEqualTo(result); 
    }

    @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.findByName("spring2").get(); 

            assertThat(result).isEqualTo(member1);
        }

    @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();

        assertThat(result.size()).isEqualTo(3); 
    }
}

👉result

이제 다 녹색불이 뜬다.

지금은 MemoryMemberRepository를 구현해두고 테스트 코드를 짰는데, 반대로

테스트 코드를 미리짜면서 어떤걸 테스트할지 미리 설계하고 MemoryMemberRepository를 구현하는 방법도있다.

틀을 먼저 짜놓는것을 테스트 주도 개발이라고 해서 TDD 라고한다.

테스트를 짜놓고 구현 클래스를 만들어서 돌려보는 거다.

테스트가 하나면 상관없는데, 수십, 수백개라면 어떻게 해야할까?

이렇게 빨간 부분을 클릭해서 한꺼번에 하거나, 아니면 빌드를 해도되고,

그래들 W 띄우고 테스트 해봐도 된다.

그러면 테스트를 다 자동으로 돌려준다.

이 테스트 코드 없이 개발하는거는, 나혼자 개발할때는 어떻게든 돌아가겠지만 정말 많은사람들과 함께 개발할때는, 그리고 소스코드 베이스가 몇만, 몇십만 라인이 넘어가기 시작하면 사실 테스트 코드없이 개발하는게 거의 불가능하다.

물론 할 수는 있는데, 정~말 문제가 많다. 그래서 테스트 관련해서는 꼭 깊게 공부해야한다.

 

출처 : 인프런의 김영한 선생님 강의를 정리한 글입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

'Java > 스프링 입문' 카테고리의 다른 글

13. 회원 서비스 테스트  (0) 2021.06.04
12. 회원 서비스 개발  (0) 2021.06.04
10. 회원 도메인과 리포지토리 만들기  (0) 2021.06.04
9. 비즈니스 요구사항 정리  (0) 2021.06.04
8. API  (0) 2021.06.04

댓글

Designed by JB FACTORY