Java/Spring & Spring Boot

Unit test (단위 테스트)와 테스트 종류

마손리 2023. 5. 1. 00:57

단위 테스트

위의 그림에서 알수 있듯이 단위 테스트는 가장 작은 단위인 메서드 단위로 테스트하는 것을 말한다.

 

F.I.R.S.T 원칙

단위 테스트를 위한 테스트 케이스를 작성하기 위해 참고할 수 있는 가이드 원칙으로 F.I.R.S.T 원칙을 참고할 수 있다.

 

  • Fast(빠르게)

일반적으로 작성한 테스트 케이스는 빨라야 한다.

 

  • Independent(독립적으로)

각각의 테스트 케이스는 독립적이어야 한다.

 

  • Repeatable(반복 가능하도록)

테스트 케이스는 어떤 환경에서도 반복해서 실행이 가능해야 된다.

 

  • Self-validating(셀프 검증이 되도록)

단위 테스트는 성공 또는 실패라는 자체 검증 결과를 보여주어야 한다.

 

  • Timely(시기적절하게)

단위 테스트는 테스트하려는 기능 구현을 하기 직전에 작성해야 한다.

 

 

JUnit

JUnit은 Java 언어로 만들어진 애플리케이션을 테스트하기 위한 오픈 소스 테스트 프레임워크로서 사실상 Java의 표준 테스트 프레임워크이다.

 

 

JUnit 사용

@Component
public class CustomBeanUtils<T> {//source:업데이트를 요청할 엔티티,destination:기존의 데이터가 들어있는 엔티티
    public T copyNonNullProperties(T source, T destination) {
        if (source == null || destination == null || source.getClass() != destination.getClass()) {
            return null;
        }

        final BeanWrapper src = new BeanWrapperImpl(source);
        final BeanWrapper dest = new BeanWrapperImpl(destination);

	//업데이트 엔티티의 필드가 Null값이면 무시하고 데이터가 있다면 기존의 엔티티에 해당필드값만 덮어씌운다.
        for (final Field property : source.getClass().getDeclaredFields()) {
            Object sourceProperty = src.getPropertyValue(property.getName());
            if (sourceProperty != null && !(sourceProperty instanceof Collection<?>)) {
                dest.setPropertyValue(property.getName(), sourceProperty);
            }
        }

        return destination;
    }
}

위의 메서드는 PATCH 요청으로 업데이트할 정보만을 뽑아 기존에 존재하는 엔티티에 덮어씌우는 작업을 수행한다.

 

위 메서드의 3가지의 단위테스트를 진행

public class CustomUtilTest {
    static Member preModifyMember = new Member(); // 1.
    static Member targetMember = new Member();
    static Coffee preModifyCoffee = new Coffee();
    static Coffee targetCoffee = new Coffee();

    @BeforeAll // 2.
    public static void init(){
        preModifyMember.setMemberId(1l); // 3.
        preModifyMember.setPhone("010-1111-1111");
        preModifyMember.setMemberStatus(Member.MemberStatus.MEMBER_ACTIVE);

        targetMember.setMemberId(1l);
        targetMember.setName("Mason");
        targetMember.setEmail("mason@test.com");
        targetMember.setPhone("010-2222-2222");
        targetMember.setMemberStatus(Member.MemberStatus.MEMBER_SLEEP);

        preModifyCoffee.setCoffeeId(1l);
        preModifyCoffee.setCoffeeStatus(Coffee.CoffeeStatus.COFFEE_SOLD_OUT);

        targetCoffee.setCoffeeId(1l);
        targetCoffee.setCoffeeCode("AAA");
        targetCoffee.setPrice(4000);
        targetCoffee.setEngName("Americano");
        targetCoffee.setKorName("아메리카노");
    }
}
  1. 테스트에서 사용할 인스턴스들 생성. Member와 Coffee클래스 각각 업데이트를 진행할 정보를 담은 엔티티와 업데이트를 받을 엔티티를 생성.
  2. @BeforeAll 에너테이션으로 모든 테스트 이전에 해당 메서드를 실행한다. 이때 static으로 정적 메서드로 작성해주어야 하며 이를 위해서 전역 필드들도 정적 필드로 작성한다.
    또한 @BeforeEach를 사용하여 해당 클래스내의 각각의 테스트 메서드 실행 이전에 특정한 작업을 수행하는 것도 가능하다.
  3. 각 인스턴스에 필요한 값들 전달(builder 패턴 사용 추천).

 

public class CustomUtilTest {
   ...
   ...
    @DisplayName("A Member info updated") // 1.
    @Test // 2.
    public void modifyMemberTest(){
    	// 3.
        CustomBeanUtils<Member> customBeanUtils = new CustomBeanUtils();

	// 4.
        Member postModifiedMember = customBeanUtils.copyNonNullProperties(preModifyMember,targetMember);
		
        // 5.
        assertAll(
        	// 6.
                ()->assertEquals(postModifiedMember.getMemberId(), preModifyMember.getMemberId()),
                ()->assertNotEquals(postModifiedMember.getName(), preModifyMember.getName()),
                ()->assertNotEquals(postModifiedMember.getEmail(), preModifyMember.getEmail()),
                ()->assertEquals(postModifiedMember.getPhone(), preModifyMember.getPhone()),
                ()->assertEquals(postModifiedMember.getMemberStatus(), preModifyMember.getMemberStatus())
        );
    }
	...
    ...
}
  1. 해당 테스트의 이름을 정한다.
  2. 해당 메서드가 JUnit 테스트 메서드라고 선언한다. 해당 메서드는 void로 작성되어야한다.
  3. 테스트를 진행할 객체를 생성. 테스트 시작전에 생성할 수 있지만 각 테스트별로 다른 타입 매개변수가 주어지므로 따로 생성.
  4. 생성된 객체들을 실제로 사용하는 것과 같이 사용
  5. assertAll 메서드를 사용하지 않을 경우 테스트 진행중에 하나의 테스트에서 실패 할경우 그 즉시 테스트를 중단하기 때문에 assertAll 메서드를 사용하여 모든 테스트를 진행한다.
  6. 실제의 테스트가 이루어진다. 적절한 assert 메서드들을 이용하여 예상한 결과값이 나오도록 코드를 작성한다.

 

테스트 결과

위와 같이 테스트를 통과한 메서드들과 아닌 메서드, 그리고 실패한 테스트와 메세지까지 JUnit을 이용하여 확인이 가능하다.

 

 

Hamcrest 

Hamcrest는 JUnit 기반의 단위 테스트에서 사용할 수 있는 Assertion Framework이다. 

JUnit보다 가독성이 좀더 좋은 메서드들을 사용할 수 있다.

 

위의 예제코드를 Hamcrest를 이용하여 수정하면,

public class CustomUtilTest {
   ...
   ...
    @DisplayName("A Member info updated") 
    @Test 
    public void modifyMemberTest(){
        CustomBeanUtils<Member> customBeanUtils = new CustomBeanUtils();
        Member postModifiedMember = customBeanUtils.copyNonNullProperties(preModifyMember,targetMember);
		
        assertAll( // JUnit
        	// Hamcrest
                ()->assertThat(postModifiedMember.getMemberId(), is(preModifyMember.getMemberId())),
                ()->assertThat(postModifiedMember.getName(), is(equalTo(preModifyMember.getName()))),
                ()->assertThat(postModifiedMember.getEmail(), is(notNullValue()))
                ...
                ...
        );
    }
	...
    ...
}

assertAll은 JUnit 메서드이다.

Hamcrest는 assertThat 메서드를 이용, 이후 is() 메서드와 다른 메서드들을 이용하여 좀더 문장 형태의 코드로 만들수 있다. (사실 그렇게 큰 차이점을 모르겠다...)

 

 

해당 자료 깃헙

https://github.com/Mason3144/Junit_test_practice

 

 

참고

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

Hamcrest와 JUnit api의 메서드들은 위와 같이 static으로 작성되어 있다. 

위와 같이 static import 문을 사용하여야 하며 인텔리J의 자동완성 기능이 안되니 참고하자.