Back-End/Java Spring Boot

[Spring Boot] 테스트 코드 작성해보기

Hoplin 2022. 3. 13. 14:51
반응형

테스트코드를 작성하는 궁극적인 목표는 잘 작동하는 깔끔한 코드를 얻기 위함이다. 왜 테스트코드를 통해 깔끔한 코드를 얻을 수 있을까? 그 이유는 테스트를 하기 위해서 애플리케이션 코드를 쉽게 작성하게 되기 때문이다. 아래와 같은 service요소의 MemberService클래스가 있다고 가정하자

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

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

public class MemberService {

    private final MemberRepository memberRepository;
    //Dependency Injection을 위해서 외부로부터 초기화를 받음
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    //회원가입
    // 같은 이름의 회원이 존재하지 않는다라는 가정
    public Long join(Member member){
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m ->{
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    //전체회원조회
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberid){
        return memberRepository.findById(memberid);
    }
}

이 코드를 가지고 테스트 케이스를 작성해 보자. Intelli J 기준으로 테스트 케이스를 자동으로 만들어 주는 단축키는 ⌘ + Shift + T이다. 테스트 케이스 생성을 하면 아래와 같이 생성된것을 볼 수 있다. 그냥 직접 파일을 생성하고 싶은 경우에는 test/java/(프로젝트패키지명)/service/MemberServiceTest.class 를 만들어 준다. 테스트 코드는 아래와 같이 작성해 주었다.

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memoryMemberRepository;

    @BeforeAll
    static void beforeAll(){
        System.out.println("Start Test");
    }

    @AfterAll
    static void afterAll(){
        System.out.println("End Test");
    }

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

    @AfterEach
    public void afterEach(){
        memoryMemberRepository.clearStore();
        MemoryMemberRepository.clearSequence();
    }

    @Test
    void join() {
        //given : 주어진 상황
        Member member = new Member();
        member.setName("spring");

        //when :
        Long saveId = memberService.join(member);

        //then :
        Member findMember = memberService.findOne(saveId).get();
        Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void duplicateMemberFind(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");
        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, ()->memberService.join(member2));
        Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        /*
        try{
            memberService.join(member2);
            fail();
        }catch (IllegalStateException e){
            Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }
        */

    }
    @Test
    public void findOne() {
        System.out.println(memberService.findMembers());
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring2");
        memberService.join(member1);
        memberService.join(member2);
        Assertions.assertThat(memberService.findOne(member1.getId()).get().getId()).isEqualTo(1);
    }
}

위 코드에서 사용된 몇가지 핵심 개념을 살펴보자

@Test

이 어노테이션은 JUnit에서 테스트 케이스로 인식하게끔 하는 어노테이션이다.

@BeforeEach

이 어노테이션을 붙이면 테스트 메서드 실행하기 이전에 수행된다.

@AfterEach

이 어노테이션을 붙이면 테스트 메소드 실행 이후에 수행된다.

@BeforeAll

이 어노테이션을 붙이면 테스트 메소드를 초기화할 때 딱 한번 수행되는 메소드이다. 이 메소드는 static으로 선언되어야 한다. 선언 예시는 아래에 있다

@AfterAll

이 어노테이션을 붙이면 테스트 메소드를 실행한 후 딱 한번 수행되는 메소드이다. 이 메소드 또한 static으로 선언되어야한다. 선언 예시는 아래에 있다.

 

@BeforeAll
    static void beforeAll(){
        System.out.println("Start Test");
    }

@AfterAll
    static void afterAll(){
        System.out.println("End Test");
 }

org.assertj.core.api.Assertion VS org.junit.jupiter.api.Assertions

assertThat VS assertEquals

두가지 메소드 모두 Expected값(예상한 값)과Actual값(실질적인 값)을 비교해서 올바른 값이 나오는지를 확인하는 메소드이다. 위 테스트 코드에서 finalOne() 테스트 메소드를 assertThat, asserEquals 각각 사용해서 구현해 보자

 

< assertThat() >

@Test
    public void findOne() {
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring2");
        memberService.join(member1);
        memberService.join(member2);
        Assertions.assertThat(memberService.findOne(member1.getId()).get().getId()).isEqualTo(1);
        
    }

< assertEquals() > 

@Test
    public void findOne() {
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring2");
        memberService.join(member1);
        memberService.join(member2);
        org.junit.jupiter.api.Assertions.assertEquals(memberService.findOne(member1.getId()).get().getId(),1);
    }

org.junit.jupiter.api.Assertions.assertThrows

assertThrows는 두개의 인자를 받는다

  • param1 : 예외 클래스 정보
  • param2 : 람다식

assertThrows는 두번째 인자를 실행했을떄 예외타입이 첫번째 인자와 동일한지 검사하는 메소드이다.

IllegalStateException e = assertThrows(IllegalStateException.class, ()->memberService.join(member2));
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

 

만약 assertThrows를 사용하지 않고 처리할 경우에는 아래와 같이 예외처리문을 작성해 주는 방법도 있다.

	try{
            memberService.join(member2);
            fail();
        }catch (IllegalStateException e){
            Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }

위 코드에서 사용된 fail()메소드의 경우에는 AssertionError예외를 발생시키며 테스트 실패 처리를 하게 해주는 메소드이다.

 

조금 번외로 지금 같은 경우에는 비즈니스 로직을 작성하고 테스트 코드를 작성하였다. 반대로 테스트 코드를 작성하고 그것을 기반으로 비즈니스 로직을 작성하는 개발 방식을 TDD(Test Driven Development)라고 한다,

반응형