목차

JUnit Test Case

개요

JUnit 을 이용하여 Test Case 를 작성하고 실행하는 방법을 안내한다.

설명

환경설정

Springframework 2.5 에서는 JUnit 4.5 버전이 동작하지 않으므로, JUnit 4.4 버전을 사용하도록한다.

Maven Project 인 경우에는 아래와 같은 dependency 를 설정하면 된다.

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.4</version>
            <scope>test</scope>
        </dependency>
 
        <dependency>
            <groupId>org.unitils</groupId>
            <artifactId>unitils</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>

사용법

Test Case 생성

일반적인 Java Class 를 New 하는 식으로도 작성할 수 있으며, JUnit 라이브러리가 사용된 경우 자동으로 JUnit Test Case 로 인식한다.
아래는 Eclipse New Wizard 를 이용해 Test Case 를 생성하는 방법이다.

  1. JUnit 을 사용하기 위한 Wizard 를 선택하고 Next를 클릭한다.
  2. Test Case 의 이름을 입력하고 Finish를 클릭한다.
    Test Case 의 이름은 보통 대상 프로그램 뒤에 Test 를 붙인다.
    만약, 대상 프로그램 별로 Test 코드가 여러 개일 경우에는 OOOOTest_xxxx 와 같은 식으로 _ 를 붙여서 구분한다.
    이러한 Naming 의 규칙은 정해진 것은 아니나 일반적인 경우이니 참조하면 된다.

Test Case 실행

테스트 프로그램 오른 쪽 버튼 클릭 후 Run As… 를 이용하여 수행한다.

Test Case 결과 확인

아래와 같은 화면으로 테스트 수행 결과를 확인할 수 있으며, 성공적으로 수행한 경우에 Green Bar 가 나타나며, 실패한 경우에는 Red Bar 로 실패 원인을 확인할 수 있다.

샘플

JUnit 4를 이용해 단위 테스트를 개발하는 경우 기본적인 가이드 프로그램이다. 또한, JUnit 4 와 함께 사용하여 유용한 유틸을 제공하는 Unitils 를 활용하는 모습도 일부 포함한다.

Constructor Test

단순한 로직의 경우 굳이 테스트하지 않는다. 단, 값을 초기화하거나 Load 하는 경우가 있는 때에는 테스트를 수행한다.

Setter / Getter Test

단순한 Set/Get 은 굳이 테스트 하지 않는다. 로직이 있는 경우에는 로직을 별도의 메소드로 분리하고 이를 테스트하는 것이 바람직하다.

Boundary Test

Null 에 대한 확인, Default 값에 대한 확인, 범위가 있는 경우 MIN/MAX 값에 대한 확인이 목적이다. 이 경우 결국엔 NullPointer 가 발생하지 않도록 방어로직 혹은 검증 로직을 보완하는 것이 바람직하다.

Exception Test

원하는(예상하고 있는) 예외가 실제로 발생하는지 확인한다. try/catch 문이 아닌 단순 어노테이션 설정만으로 간단하게 확인할 수 있다.

@Test (expected = IndexOutOfBoundsException.class)
public void testException() {
       throw new IndexOutOfBoundsException();
}

Equality Test

value 와 reference 비교
@Test
public void testReferenceEqual() {
      String string1 = new String("somevalue");
      String string2 = string1;
      assertEquals("두 객체의 값이 같음", string1, string2); 
      assertSame("두 객체의 주소값이 같음", string1, string2);    
}
숫자 값 비교 (기본적인 비교는 동일하나, 소숫점 자리 이하 비교)
@Test
public void testDoubleEqual() {
      double value1 = 10 / 3;
      double value2 = 3.33;
      assertEquals("소수 둘째자리까지 같음", value1, value2, 2);
}
리스트/배열의 비교

JUnit 의 기본 기능은 순서가 같을 경우에만 참을 리턴한다.

@Test
public void testListEqual() {
      List<Integer> value1 = Arrays.asList(3, 2, 1);
      List<Integer> value2 = Arrays.asList(3, 2, 1);
      assertEquals("두 리스트의 값과 순서가 같음", value1, value2);
}
 
@Test
public void testArrayEqual() {
      String[] value1 = new String[] {"A", "B", "C"};
      String[] value2 = new String[] {"A", "B", "C"};
      assertArrayEquals("두 배열의 값과 순서가 같음", value1, value2);
}

Unitils 를 추가로 활용하여 순서와 디폴트 값과 상관없이 비교한다.

@Test
public void testReflectionEqualLenientOrder() {
      List<Integer> myList = Arrays.asList(3, 2, 1);
 
      assertReflectionEquals(Arrays.asList(3, 2, 1), myList);
 
      assertReflectionEquals("순서 무시하고 값이 같은지 비교", Arrays.asList(1, 2, 3), myList, LENIENT_ORDER);
}
 
@Test
public void testAssertReflectionEqualsIgnoringDefault() {
      assertReflectionEquals("디폴트 값 무시하고 같은지 비교",
 
                                    new UserVo(1, "name", null), new UserVo(1, "name", "description1"), IGNORE_DEFAULTS);
}
Value Object 의 비교

JUnit 만을 사용하는 경우에는 Value Object 를 비교하기 위해서는 각각의 attribute 단위로 하나하나 비교해야 한다.

 @Test
 public void testObjectEqual() {
      UserVo user1 = new UserVo(100, "name", "description");
      UserVo user2 = new UserVo(100, "name", "description");
 
      assertEquals(user1.getId(), user2.getId());
      assertEquals(user1.getName(), user2.getName());
      assertEquals(user1.getDescription(), user2.getDescription());
 }

Unitils 를 함께 사용하는 경우에는 attribute 단위로 자동으로 하나하나 비교해 주므로 편리하다.

@Test
public void testAssertReflectionEqualsFieldByField() {
 
      UserVo user1 = new UserVo(100, "name", "description");
      UserVo user2 = new UserVo(100, "name", "description");
 
      assertReflectionEquals("객체를 구성하는 attribute를 하나하나 자동으로 비교", user1, user2);
}

테스트 수행 전후 처리

메소드 혹은 클래스 단위로 테스트 수행 전후 처리를 수행한다. JUnit 초기 버전에서는 SetUp / TearDown 이라는 메소드로 처리했었는데 현재는 아래의 Annotation 으로 제어가 쉬워졌다.

@BeforeClass/AfterClass : 단위 테스트 클래스 실행 전/후에 단 한번만 수행 @Before/After : 단위 테스트 클래스 내부의 각 테스트 메소드 실행 전후마다 수행

아래는 StringBuffer 를 선언해두고, 실행이 될 때마다 특정 스트링을 Append 하여 전체 순서를 알아볼 수 있는 예제이다.

public class SetupTest {
 
     static StringBuffer sb;
 
     @BeforeClass
     public static void doBeforeClass() {
          sb = new StringBuffer();
          sb.append("BeforeClass/");
 
          assertEquals("BeforeClass/", sb.toString());
     }
 
     @Before
     public void doBeforeMethod() {
          sb.append("BeforeMethod/");
     }
 
     @After
     public void doAfterMethod() {
          sb.append("AfterMethod/");
     }
 
     @AfterClass
     public static void doAfterClass() {
          sb.append("AfterClass/");
          assertEquals("BeforeClass/BeforeMethod/testFirst/AfterMethod/BeforeMethod/testSecond/AfterMethod/AfterClass/", sb.toString());
     }
 
     @Test
     public void testFirst() {
          assertEquals("BeforeClass/BeforeMethod/", sb.toString());
          sb.append("testFirst/");
          assertEquals("BeforeClass/BeforeMethod/testFirst/", sb.toString());
     }
 
     @Test
     public void testSecond() {
          assertEquals("BeforeClass/BeforeMethod/testFirst/AfterMethod/BeforeMethod/", sb.toString());
          sb.append("testSecond/");
          assertEquals("BeforeClass/BeforeMethod/testFirst/AfterMethod/BeforeMethod/testSecond/", sb.toString());
     }
}

Parameterized Test

파라미터 값만 바꿔가면서 동일한 테스트를 반복해서 수행하고 싶을 때 유용하다. 아래 예제는 비밀번호에 대해 유효한지를 체크하는 프로그램을 테스트하는 것인데, 입력 값을 여러개로 지정한 후 한번에 테스트하는 모습이다.

@RunWith(Parameterized.class)
public class ParameterizedTest {
 
     private String password;
     private boolean isValid;
     private static PasswordValidator validator;
 
     @BeforeClass
     public static void setUp() {
          validator = new PasswordValidator();
     }
 
     public ParameterizedTest(String password, boolean isValid) {
          this.password = password;
          this.isValid = isValid;
     }
 
     @Parameters
     public static Collection passwords() {
          return Arrays.asList(new Object[][] { { "1234qwer", true }, {"12345678", false}, {"1q2w3e4r", true} });
     }
 
     @Test
     public void isValidPasswordWithParams() {
          assertEquals(validator.isValid(this.password), this.isValid);
     }
}
 
// 테스트 대상인 Inner Class 
class PasswordValidator {
 
     public boolean isValid(String password) {
          boolean result = false;
          int letterCnt = 0;
          int digitCnt = 0;
 
          for (int i = 0; i < password.length(); i++) {
               char c = password.charAt(i);
               if (Character.isLetter(c)) letterCnt++;
               else if (Character.isDigit(c)) digitCnt++;
          }
 
          // 8자리 이상이고(입력 때 체크되지만) 문자와 숫자가 적어도 한 개씩은 있어야 함
          if (password.length() >= 8 && letterCnt > 0 && digitCnt > 0)
               result = true;
 
              return result;
          }
}

Timeout Test

테스트에 걸리는 시간을 지정하여, 해당 시간이 넘은 경우 Fail 처리할 수 있다.

@Test (timeout = 1)
public void testTimeout() {
}

Test Ignore

테스트를 SKIP 하고 싶을 때 사용한다. 클래스/메소드 단위 모두 가능하다.

@Ignore ("테스트 수행하지 않도록 설정 함 (실제론 상세 원인을 표시)")
@Test
public void testIgnore() {
}