JAVA/Unit Test2009. 2. 16. 11:33

머리로 아는 내용을 설명한다는 것이 정말 답답하더군요. 진도도 나가지 않고 답답한 맘뿐입니다.
차라리 단위테스트 설명을 올리는 것보다 Junit 테스트를 먼저해서 올렸으면 좀더 수월하게 했을지로 모른다는 생각이 드네요.
썰은 이쯤에서 마치고 테스트로 들어 가겠습니다.

  • 어떤 코드건 작성하기 전에 실패하는 자동화 테스트를 작성하라.
  • 중복을 제거하라

TDD의 들어가는 글에 나와있는 규칙입니다. 그리고 위의 두개가 TDD의 주제 이기도 합니다.
위에서 열거한 자동화된 테스트를 위한 툴이 저희가 공부할 Junit입니다.

JUnit은 현재 4.5버젼까지 나왔으며 단위테스트 with JUnit 에서 사용하는 버젼은 3.8x을 사용하고 있습니다.

  • 3.8x 버젼의 권장사항
    1. TestCase를 확장한다.
    2. 메서드명은 test로 시작해야 한다.
      만약 테스트 하려는 메서드가 createMethod()라면 testCreateMethod()로 명명할 것을 권장한다.

책에 부록으로 있는 Eclipse에서 Junit사용하기를 보시면 신호등관련 프로그램의 테스트가 나와 있습니다.

package TrafficLightModel;

import junit.framework.TestCase;


public class TrafficLightModelTest3_X extends TestCase {

      public void getTrafficLightModel() {

            TrafficLightModel a = new TrafficLightModel();
            assertTrue(a.getRed());
            //assertTrue(a.getYellow());
            //assertTrue(a.getGreen());

      }

}

얼핏 보아도 쉬운 내용임을 알 수 있습니다. getRed()메서드를 포함한 TrafficLightModel 클래스가 구현되어 있다면,
Junit를 실행하기 전까진컴파일에 있어서도 아무 문제를 일으키지 않습니다.


Failure_Trace 창의 에러메세지를 보시면 test를 찾을 수 없다고 나옵니다.
getTrafficLightModel() 메서드의 이름을 testTrafficLightModel() 로 바꿔 주시면 반가운 초록막대를 보실 수 있습니다.
즉, 권장사항이라고 얘기했지만 위의 두 규칙을 지키지 않으면 테스트 할 수 없는 것입니다.

Junit 3.8x 버젼때의 제약은 4.로 넘어가면서 자바5의 어노테이션과 만나 위의 권장사항은 따르실 필요는 없게 되었습니다.
물론 기존의 테스트소스를 Junit 최신판으로 다운받아 실행하는데 문제는 없습니다.
그저 좀더 간편해 졌기에 맛보기용으로 조금 올려 봅니다.

package TrafficLightModel;

import org.junit.Test;
import static org.junit.Assert.*;

public class TrafficLightModelTest4_X {

	@Test
	public void getTrafficLightModel() {
		TrafficLightModel a = new TrafficLightModel();
		assertTrue(a.getRed());
	}
}

클래스 계층구조는 더이상 필요하지 않고, 테스트로 작동할 메서드도 새롭게 정의된 @Test 어노테이션만 기술하시면 됩니다.

다시 책으로 돌아가 2장의 첫테스트와 만나 보겠습니다.
2장의 첫테스트는 정수배열을 받아들여 그중에서 큰값을 리턴해주는 프로그램입니다.

package first;

public class Largest {
	
	public int largest(int[] list){
		int max = Integer.MAX_VALUE;
		for (int index=0; index <list.length;index++) {
			if (list[index] > max)
				max = list[index];
		}
		return max;
	}
	
	public static void main (String args[]) {
	    Largest l = new Largest();
		System.out.println(l.largest(new int[] {7,8,-20,0,8}));
	}
}

저의 테스트 방식입니다. main()메서드를 사용해서 리턴값을 확인한다거나 println()문을 사용하여 제가 만들어 놓은 값을 확인합니다.
요즘도 값을 추출하거나 특정단어를 인식하는 간단한 메서드를 만들어 테스트할때 많이 사용합니다.

그리고 보통의 경우 테스트는 다음과 같은 방식으로 진행될 것입니다.

  1. 재빨리 테스트를 하나 추가한다.
  2. 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다.
  3. 코드를 조금 바꾼다.
  4. 모든 테스트를 실행하고 전부 성공하는지 확인한다.

위의 말중에 재빨리와 실패라는 단어가 눈에 거슬릴지도 모르겠습니다만 잠시 무시해 주세요.

Lagest 클래스로 눈을 돌려 저희가 일련의 배열에서 큰값을 리턴해 주는 메서드를 작성하려 할때 메서드를 만들어
편한데로 확인하려 할것입니다. 위의 메서드에서도 원했던 값은 배열중 제일큰 8이란 값을 리턴해주길 바랬지만
끔찍하게도 2,147,483,647 이란 값을 리턴해 줍니다.

절대 실패할것이 아니고 단번에 제가 원한 답이 나와주길 바랍니다만 실패할때가 많습니다.

우연찮게 성공했다고 하여도 한번에 여러개의 값을 동시에 입력, 삭제할때 혹은 동일 값이 들어가 있거나 등의
앞장에서도 살짝쿵 설명했지만 경계조건의(대표값: null or 0)의 사이에서 의도치는 않았겠지만 실패요소들을
상당수 안고 있습니다.

위의 2번째 실패하는지 확인한다의 3번째 코드를 바꾼다 횟수가 많았던 만큼 현업 혹 윗상사의 눈밖에 날 위험은 현저히 줄어들겁니다.
2~3의 단계 무한반복후 의도한 값이 제대로 나오면 보통의 테스트는 여기서 끝나게 됩니다.
허나 하나 더 남아 있습니다.

5. 리팩토링을 통해 중복을 제거한다.

뭔가 앞에 말이 붙긴 했지만 어디서 봤던 말입니다. 중복을 제거 한다. 무슨 중복을 제거하는지는 차차 보시게 될듯합니다.
위에 써놓은 말은 토시하나 안틀리고 테스트 주도 개발에 나오는 내용입니다.
리팩토링을 통한 중복제거는 앞으로 소개하는 것으로 하고 넘어가겠습니다. (제 레벨이 아직;;; )

package first;

import junit.framework.TestCase;

public class LargestTest3_X extends TestCase {

	Largest l = new Largest();
	public void testGetLargest() {
		
		assertEquals(8,l.largest(new int[] {7,8,-20,0,8}));
	}
}

JUnit에서 다음같은 코드로 테스트를 하셨다면 Failure Trace창에서 이란 말을 보실 수 있습니다.

junit.framework.AssertionFaildError:expected:<8> but was:<2147483647>
at first.LargestTest3_X.testGetLargest(LargestTest3_X.java:10)

int max = Integer.MAX_VALUE;
를 사용한 부분을 max = 0으로 수정을 한 후 테스트를 하면 또다시 우리를 기쁘게 해주는 초록막대를
확인가능합니다.

그러나 문제가 있습니다. 배열의 정수가 모두 -값이라면 얘기가 달라집니다.
배열에는 (-41,-6,-30,-22) 값이 있을때 제가 원한 -8값을 돌려 주는 것이 아니라 0을 리턴하니
main()메서드로 얼핏 확인했다며 놓치고 지나칠 수 있었을 문제였습니다만

junit.framework.AssertionFaildError:expected:<8> but was:<0>
at first.LargestTest3_X.testGetLargest(LargestTest3_X.java:11)

네~ 실패했습니다. max = 0 이었던 값도 문제가 있었습니다.
max의 초기값을 음수보다 작게 만들어야 문제가 없음이 판명되었습니다. Integer.MIN_VALUE를 사용해야
다시 모두가 행복해지는 결과를 볼 수 있습니다.

공배열을 넣고 원하는 결과를 리턴 받을 수 있을까요??

package first;

import junit.framework.TestCase;

public class LargestTest3_X extends TestCase {

	Largest l = new Largest();
	public void testGetLargest() {
		assertEquals(8,l.largest(new int[] {7,8,-20,0,8}));
		assertEquals(-6,l.largest(new int[] {-41,-6,-30,-22}));
	}
	public void testGetEmpty() {
		l.largest(new int[] {});
		fail("테스트를 실패처리함.");
	}
}

네~ 실패했습니다. 책에 나와있는 설명으로 보면

' 단지 테스트를 생각해 보는것만으로 코드의 설계를 바꿔야한다는 것을 알아냈다는 사실에 주목하라.
이것은 전혀 특별한 일이 아니며, 실제로 우리가 테스트를 이용해 얻고자한 이득중의 하나이다.'

제가 플젝현장에서 메서드작성중 이런 결과를 얻어내고 메서드에 반영조치 했다면 충분히 특별하고
충분히 자랑질할 일이었을 듯 합니다만 책에선 특별한 일이 아니고 통상의 일이라 하니 그렇게 느낄때까지
사용할 수 밖에 없겠습니다.

Largest.java
int max = Integer.MIN_VALUE;
	if (list.length == 0) {
	    throw new RuntimeException("공배열입니다.");
         }

테스트에서 발생하는 * 예상된 예외에 대한 처리 *를 할때 fail을 사용한다고 합니다.

LargestTest3_X.java
public void testGetEmpty() {
		try{
			l.largest(new int[] {});
			fail("테스트를 실패처리함.");
		}catch(Exception e){
			assertTrue(true);
			
		}
	}

코드를 다음과 같이 수정후 다시한번 행복의 초록막대를 보실 수 있습니다.

package first;

import org.junit.Test;
import static org.junit.Assert.*;

public class LargestTest4_X {
	Largest l = new Largest();
	
	@Test
	public void GetLargest() {
		assertEquals(8,l.largest(new int[] {7,8,-20,0,8}));
		assertEquals(-6,l.largest(new int[] {-41,-6,-30,-22}));
	}
	@Test (expected=RuntimeException.class)
	public void GetEmpty() throws Exception{
			l.largest(new int[] {});
	}
}

위의 코드는 LargestTest3_X.java와 동일합니다.
try/catch 문을 사용하지 않고 expected 매개변수를 사용함으로 Exception 처리가 조금 달라졌습니다.

Posted by B정상