jhhan의 블로그

Spring - Test Code 작성 본문

Spring

Spring - Test Code 작성

jhhan000 2020. 9. 12. 16:36

이번에는 Test Code에 대해 작성해보려 합니다.

 

개발을 진행함에 따라 곧바로 적용해서 로직을 수정할 수도 있겠지만,

항상 곧바로 적용할 수 있지는 않습니다.

그럴 때 테스트 코드를 만들어서 만든 로직이 정상적으로 동작하는지 알 수 있게 할 수 있습니다.

 

그럼 먼저 테스트 해볼 대상을 만들어보겠습니다.

정말 간단한 회원관리를 해볼 것입니다.
(아이디와 이름만 들어갈 것입니다.)

참고로 지난 번 글인 Spring - Static 글에서 사용한 프로젝트를 그대로 가져와서 사용할 것입니다.

jhhan009.tistory.com/50

프로젝트 구조입니다.

Member, MemberRepository, MemoryMemberRepository를 만들 것입니다.

먼저 Member 클래스입니다.

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;
    }
}

아이디와 이름만 받을 수 있게 설정했습니다.

그리고 getter 와 setter를 설정했습니다.

 

다음은 MemberRepository입니다.(인터페이스)

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
}

총 4개의 메소드가 있습니다.

  • save: 저장을 합니다.
  • findById: id로 검색합니다.
  • findByName: name으로 검색합니다.
  • findAll: 저장되어 있는 모든 회원을 조회합니다.

간단하죠?

 

다음은 MemoryMemberRepository 클래스입니다.

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

MemberRepository를 구현합니다. 

그리고 추가로 clearStore()라는 메소드를 추가했습니다.

store에 저장된 회원들을 모두 지워주는 역할을 합니다.

 

여기서 Optional<>은 객체의 반환값이 null일 때도 대비할 수 있게 합니다.

findById와 FindByName 메소드는 반환값이 없을 경우도 있기 때문에 null을 반환하는 경우를 생각해야 합니다.

Optional을 사용하면 null을 반환해도 오류가 생기지 않습니다.

(사실 Optional에 대해서는 조금 더 깊숙히 알아야 할 필요가 있는데, 아직 제가 Optional을 잘 몰라서 이정도만 설명하겠습니다. 나중에 기회가 된다면 Optional에 대해서 글을 써보겠습니다....... -- 안 할 듯)

 

자 여기까지 작성을 완료했음니, 만든 메서드가 제대로 작동하는지 확인하고 싶습니다.

근데 저거 말고 따로 만들어놓은게 없으니 당장 확인할 길이 없습니다.

그렇다고 화면단을 따로 만들기에는 시간이 오래걸립니다.(그리고 그걸 빌드 하는데에도 조금 걸립니다.)

이 때 Test Code를 사용하면 좋습니다.

src 밑에는 main과 test로 구분되어 있습니다.

이제 test 밑에 폴더로 가서 저렇게 클래스를 하나 만들어봅니다.

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

public class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
//        System.out.println("result = " + (result==member));
//        Assertions.assertEquals(result, null);
        assertThat(member).isEqualTo(result);
    }

}

그리고 먼저 save를 테스트 해보는 코드를 만들어 보겠습니다.

(assertThat을 썼을 때 성공하면 초록색으로, 실패하면 빨간색으로 뜹니다.)

아마 이런 식으로 코드가 보일텐데 빨간색으로 동그라미를 친 부분을 눌러보면 

테스트 코드를 Run할 수 있습니다. Run을 눌러봅니다.

그럼 이렇게 결과가 뜹니다. 초록색으로 떴으니 성공한 것입니다.

만약 

assertThat(member).isEqualTo(null); 이라고 바꾼 후 다시 Run을 해본다면

실패했다고 뜰 것입니다.

 

Assertions.assertEquals(result, member);

Assertions를 쓰면 결과가 맞는지 아닌지 확인할 수 있게 합니다.

참고로 Assertions 뒤에 assertEqauls를 쓰려면 org.junit.jupiter.api를 선택해야 쓸 수 있고,

Assertions.assertThat으로 쓰려면 org.assertj.core.api를 선택해야 사용할 수 있습니다.

Assert 관련해서는 조금 더 검색하는 것을 추천드립니다. (저도 assert에 대해서 잘 몰라서 이정도만 씁니다...)

 

그럼 나머지 테스트 코드 작성도 해보겠습니다.

public class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
//        System.out.println("result = " + (result==member));
        org.junit.jupiter.api.Assertions.assertEquals(result, member);
//        assertThat(member).isEqualTo(null);
    }

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

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

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

        Member member3 = new Member();
        member3.setName("spring3");
        repository.save(member3);

        List<Member> result = repository.findAll();
        assertThat(result.size()).isEqualTo(3);
    }
}

이제 각각의 Test 코드들을 Run 해보면 정상적으로 동작한다는 것을 알 수 있습니다.

 

근데 지금은 테스트 코드가 적지만, 테스트 코드가 10개만 되도 일일이 테스트를 눌러보는 것이 귀찮을 것입니다.

그럴 때는

테스트 클래스 자체를 Run 하면 됩니다.

이제 Run 해봅니다. 그러면...

오류가 뜨는 것을 확인할 수 있습니다....

보면 테스트가 제가 짠 순서대로 진행이 안되는 것을 알 수 있습니다.

findAll()이 가장 먼저 실행이 되고 있습니다.

그런 다음에 findByName를 실행하면, 이전에 저장한 것이 그대로 유지가 되기 때문에 

어떤 메소드는 멀쩡하지만, 결과는 실패로 나올 수도 있습니다.

이런 것을 대비하기 위해서는 각 테스트가 끝날 때마다 저장된 이력을 싹 지워야할 필요가 있습니다.

그래서 다음의 코드를 추가합니다.

public class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach //  Test가 하나 끝날때 마다 repository를 비워줌
    public void afterEach() {
        repository.clearStore();
    }

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
//        System.out.println("result = " + (result==member));
        org.junit.jupiter.api.Assertions.assertEquals(result, member);
//        assertThat(member).isEqualTo(null);
    }

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

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

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

        Member member3 = new Member();
        member3.setName("spring3");
        repository.save(member3);

        List<Member> result = repository.findAll();
        assertThat(result.size()).isEqualTo(3);
    }
}

afterEach라는 메소드를 만들었습니다.

그리고 어노테이션으로 @AfterEach를 넣었습니다.

이렇게 하면 각 테스트가 끝날 때마다 afterEach라는 메소드가 실행됩니다.

메소드의 내용은 저장된 이력을 싹 삭제하는 내용입니다.

이제 클래스 테스트를 다시 Run한다면

모든 테스트가 제대로 작동하는 것을 알 수 있습니다.

 

 

이렇게 테스트 코드 작성하는 법에 대해 알아봤습니다.

테스트 코드 작성을 단순하게 생각하면 생각보다는 쉽습니다.

물론 여러 어노테이션을 생각해본다면 무조건 쉽다고 할 수는 없습니다.

테스트 코드 작성을 위해 더 공부해야할 필요성도 있습니다.

 

어쨌든 이렇게 Spring에서 Test 코드 작성하는 방법에 대해 알아봤습니다.

'Spring' 카테고리의 다른 글

Spring - DI(의존성 주입)  (0) 2020.09.20
Spring - Test Code 작성(2)  (0) 2020.09.13
Spring - static  (0) 2020.09.06
Spring - textarea 관련  (0) 2020.06.23
Spring-JPA 활용(2)  (0) 2020.06.09