[spring] (5) 단위테스트와 Junit5

orca·2022년 11월 16일
0

Spring

목록 보기
5/13

Unit Test = 단위 테스트

프로그래밍에서 소스 코드의 특정 모듈이 의도된대로 정확하게 작동하는지 검증하는 절차!
즉, 모든 함수와 메소드에 대한 테스트 케이스를 작성하는 절차를 말함

main/java/com/haden/prjforstd/member 안에 작성된 클래스들

public enum Grade {
    BASIC,
    VIP
}
public class Member {
    private Long id;
    private String name;
    private Grade grade;

    public Member(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

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

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}
public interface MemberRespository {
    // 인터페이스
    void save(Member member);
    Member findById(Long memberId);
}
public class MemoryMemberRepository implements MemberRespository {
 
    private static Map<Long, Member> store = new HashMap<>();

    @Override
    public void save(Member member) {
        store.put(member.getId(), member);
    }

    @Override
    public Member findById(Long memberId) {
        return store.get(memberId);
    }

}
public interface MemberService {
    void join(Member member);

    Member findMember(Long memberId);
}
public class MemberServiceImpl implements MemberService {

    private final MemberRespository memberRespository = new MemoryMemberRepository();

    @Override
    public void join(Member member) {
        memberRespository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRespository.findById(memberId);
    }

}

메인 메서드를 이용해 테스트해보기

아래와 같이 테스트 가능함

public class MemberApp {
    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member);


        Member findMember = memberService.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("find member = " + findMember.getName());
    }
}

실행 결과

메인 메서드로 테스트 하는 것은 가독성이 떨어지고 번거로움

Java 테스팅 툴 = JUnit5 + assertJ

The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. 
Junit은 JVM에서 테스팅하기 위한 기반을 제공함
Junit

AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability and is designed to be super easy to use
우리는 테스트 코드를 향상시키는 매우 사용하기 쉬운 Assertions를 제공함!
AssertJ

GIVEN/WHEN/THEN 패턴

단위 테스트 작성시 이용되는 패턴
1개의 단위 테스트를 3가지 단계로 나누는 패턴임

given (준비) : 어떠한 데이터가 준비되었을 때

when (실행) : 어떠한 함수를 실행하면

then (검증) : 어떠한 결과가 나와야 한다. > assertion

🙋‍♀️Assertion

Assertion은 프로그램에 추가하는 참 or 거짓을 미리 가정하는 문
개발자는 해당 문이 언제나 참이라고 간주함
런타임 중에 Assertion이 거짓으로 평가되면 Assertion Failure를 초래함

JUnit5 활용해 테스팅 해보자

@Test : 이 메소드는 테스트 메서드임을 명시하는 어노테이션

main과 동일한 위치인 test/java/com/haden/prjforstd/member 에 테스트 코드 작성
빌드하면 main dir에 위치한 코드만 나가고 테스트 코드는 배포되지 않으니 안심안심

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl();

    @Test
    void join(){
        //given 이런이런게 주어졌을때
        Member member = new Member(1L, "memberA", Grade.VIP);

        //when 이렇게 했을때
        memberService.join(member);
        Member findMember = memberService.findMember(1L);

        //then 이렇게 된다
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}

JUnit5 더 활용해보기

아래와 같은 코드를 만들고 왼쪽 마우스 > Generate > Test…

@Setter
@Getter
@NoArgsConstructor
@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String image;

    @Column(nullable = false)
    private String link;

    @Column(nullable = false)
    private int price;


    public Product(ProductRequestDto requestDto) {
        if(requestDto.getTitle() == null || requestDto.getTitle().isEmpty()){
            throw new IllegalArgumentException("상품명이 없습니다");
        }
        if(requestDto.getPrice() <= 0){
            throw new IllegalArgumentException("상품 최저가가 0 이하입니다");
        }
        this.title = requestDto.getTitle();
        this.image = requestDto.getImage();
        this.link = requestDto.getLink();
        this.price = requestDto.getPrice();
    }

    public void updateProductPrice(int price){
        this.price = price;
    }
}

아래와 같이 ProductTest 클래스가 만들어짐

@Test : 이 메소드는 테스트 메서드임을 명시하는 어노테이션

class ProductTest {

    @Test
    void createProduct(){

        //given
        String title = "사과";;
        String image = "사과이미지";;
        String link = "링크";
        int price = 10000;
        ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);

        //when
        Product product = new Product(requestDto);

        //then
        assertNull(product.getId());
        assertEquals(title, product.getTitle());
        assertEquals(image, product.getImage());
        assertEquals(link, product.getLink());
        assertEquals(price, product.getPrice());
    }
}

만약 테스트 메서드가 여러개라면?

@BeforeEach : 각각의 테스트 메서드 전에 실행되는 구문

@DisplayName("") : 코드 실행시 “” 안에 들어 있는 이름으로 각각의 테스트 코드를 구분 가능

class ProductTest {

    private String title;
    private String image;
    private String link;
    private int price;

    @BeforeEach
    void setup(){
      title = "사과";
      image = "사과이미지";
      link = "https://search.shopping.naver.com/search/all?query=%EC%82%AC%EA%B3%BC&bt=-1&frm=NVSCPRO";
      price = 10000;
    }

    @Test
    @DisplayName("create")
    void createProduct(){

        //given
        ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);

        //when
        Product product = new Product(requestDto);

        //then
        assertNull(product.getId());
        assertEquals(title, product.getTitle());
        assertEquals(image, product.getImage());
        assertEquals(link, product.getLink());
        assertEquals(price, product.getPrice());
    }

		@Test
    @DisplayName("update")
    void updateProduct(){
				// 생략
    }
}

@Nested : 비슷한 테스트 코드끼리 묶고 싶을 때 사용함
이 클래스 안에 테스트 코드가 있음을 표시하는 어노테이션

class ProductTest {

    private String title;
    private String image;
    private String link;
    private int price;

    @BeforeEach
    void setup(){
      title = "사과";
      image = "사과이미지";
      link = "https://search.shopping.naver.com/search/all?query=%EC%82%AC%EA%B3%BC&bt=-1&frm=NVSCPRO";
      price = 10000;
    }

    @Nested
    @DisplayName("abnormal")
    class FailCase {
	        @Test
	        @DisplayName("null")
            void fail1(){
                //given
                String title = null;
                ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);
                //when
                Exception e = assertThrows(IllegalArgumentException.class,()->new Product(requestDto));
                //then
                assertEquals("상품명이 없습니다", e.getMessage());

            }

            @Test
            @DisplayName("빈 문자열")
            void fail2(){
                //given
                String title = "";
                ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);
                //when
                Exception e = assertThrows(IllegalArgumentException.class,()->new Product(requestDto));
                //then
                assertEquals("상품명이 없습니다", e.getMessage());
            }

    }

}

최종 활용 코드

class ProductTest {

    private String title;
    private String image;
    private String link;
    private int price;

    @BeforeEach
    void setup(){
      title = "사과";
      image = "사과이미지";
      link = "https://search.shopping.naver.com/search/all?query=%EC%82%AC%EA%B3%BC&bt=-1&frm=NVSCPRO";
      price = 10000;
    }

    @Test
    @DisplayName("normal")
    void createProduct(){

        //given
        ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);

        //when
        Product product = new Product(requestDto);

        //then
        assertNull(product.getId());
        assertEquals(title, product.getTitle());
        assertEquals(image, product.getImage());
        assertEquals(link, product.getLink());
        assertEquals(price, product.getPrice());
    }

    //비정상 케이스는 묶어서 계층구조로 표현
    @Nested
    @DisplayName("abnormal")
    class FailCase {

        @Nested
        class Title{
            @Test
            @DisplayName("null")
            void fail1(){
                //given
                String title = null;
                ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);
                //when
                Exception e = assertThrows(IllegalArgumentException.class,()->new Product(requestDto));
                //then
                assertEquals("상품명이 없습니다", e.getMessage());

            }

            @Test
            @DisplayName("빈 문자열")
            void fail2(){
                //given
                String title = "";
                ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);
                //when
                Exception e = assertThrows(IllegalArgumentException.class,()->new Product(requestDto));
                //then
                assertEquals("상품명이 없습니다", e.getMessage());
            }
        }

        @Nested
        @DisplayName("상품 최저가")
        class Price{

            @Test
            @DisplayName("0")
            void fail1(){
                //given
                int price = 0;
                ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);
                //when
                Exception e = assertThrows(IllegalArgumentException.class, ()->{new Product(requestDto);});
                //that
                assertEquals("상품 최저가가 0 이하입니다", e.getMessage());
            }

            @Test
            @DisplayName("음수")
            void fail2(){
                //given
                int price = -15000;
                ProductRequestDto requestDto = new ProductRequestDto(title, image, link, price);
                //when
                Exception e = assertThrows(IllegalArgumentException.class, ()->{new Product(requestDto);});
                //that
                assertEquals("상품 최저가가 0 이하입니다", e.getMessage());

            }
        }

    }

}

아래와 같이 깔끔하게 계층구조로 출력이 된다

0개의 댓글