http://blog.naver.com/galad/140035831360

출처 블로그 > 青山に会社を立ち上げるぞ。!!
원본 http://blog.naver.com/heopd000/80035503179

JUnit을 이용한효율적인테스트전략

Table of Contents

테스트란?

  • 자동화된 테스트란? 유형은?
    • 단위테스트 : JUnit
    • 통합/컨테이너 내부 테스트 : Cactus
    • 수락/기능 테스트 : HttpUnit.

Preview

  • 자동화된 테스트란? 유형은?
    • 자동화된 테스팅이란 특히 XP에서 중요시 되는 활동이다.
    • 물론 어느 방법론에 국한될 필요는 없으나
    • 어떤 점에서 잇점이 있는지의 여부의 판단은 중요하다고 생각한다.
    • XP의 중요한 문제는 리팩토링이다.
    • 리팩토링이란 간단하게 설명하면 오브젝트 책임의 명확성과 중복의 제거,
    • 그리고 코드의 간결함 일것이라 생각한다.
    • 자동화된 테스팅은 리팩토링을 하는 프로그래머에 확신을 준다.
    • 그리고 리팩토링은 궁극적으로 테스트를 통해서 완성이 된다.
  • 자동화된 테스트 유형
    • unit test는 가장 많이 언급되는 테스트 유형이나 이것은 전체테스팅의 일부이다.
    • 단위테스트는 통합테스트, 기능 테스트, 기타 테스트와 함께 사용되어 시스템의 동작이 의도에 맞게 동작함을 보증한다.
  • 테스트의 종류 ( 간략하게 정리 )
    • 단위테스트 : JUnit
      • 단위테스트는 단위 코드에서 문제 발생 소지가 있는 모든 부분을 테스트 하는 작업이다.
      • 보통 클래스의 public method 를 테스트 한다.
      • 좋은 단위 테스트란 모든 메서드를 테스트 하는 것이 아니라,
      • 상식 수준의 확인을 통해 단위 코드가 의도한 대로 동작하는지 여부를 판단하는 단계이다.
      • 이상적으로는 코딩전에 테스트 케이스를 작성하여 구현시 보조자료로 활용하는 것이 좋다. ( TDD의 기법 )
      • 단위테스트 후에 개발팀은 테스트를 프로젝트 테스트 스위트에 추가하여 매일 여러번 수행하고 모든 테스트를 항상 통과하게 해야 한다.
      • 기회가 된다면 Code Coverage 를 하는 것이 좋은데
      • 오픈소스로는 JCoverage 등이 있다. - 참고 : j2eestudy자료
    • 통합/컨테이너 내부 테스트 : Cactus
      • 좋은 단위 테스트는 시스템내의 복잡한 부분에 관계없이 클래스 내의 함수들을 검사하는 것이다.
      • 단위 테스트는 가능한 의존성 없이 독립적으로 처리되어야 한다.
      • Mock Object 로 테스트를 하는 경우도 있지만,
      • Cactus 는 J2EE 컨테이너에 접근하는 방법을 제공한다.
      • 컨테이너 안에서 코드 테스트가 가능하도록 하기 위해서 Cactus 는 상세하거나 또는 까다로운
      • 실제와 같은 모형을(mock-ups) 개발자에게 제공한다.
      • 이 방법은 실행되는 코드가 제품이 출시되는 환경에서 실행되기 때문에 또 다른 피드백 기준을 제공한다.
      • 컨테이너 서비스와 상호 작용하는 단일 오브젝트 경우에 컨테이너 내부 테스트를 사용하여 간편한 단위 테스트를 할 수 있다.
    • 수락/기능 테스트 : HttpUnit
      • 기능 테스트는 전체 시스템이 의도한 바대로 동작하는 지를 검사하는 과정이다.
      • 이 방법은 완성된 시스템을 고객으로부터 검사받는 방법이므로 수락 테스트라고도 한다.
      • 기능 테스트는 구조적 기능에 대하여 어떤 프로그램의 기능에 대한 시험이며 진척 상태를 확인하고
      • 이전의 테스트나 누락된 결점을 잡아내거나 미완성 또는 불완전한 부분에서 발생된 문제를 찾아내는 것이 중요하다.
      • 수락 테스트는 고객에 의해 작성된다.
      • 기능 테스트는 항상 100% 구현될 필요는 없으나 제품 출시 전에는 100% 수행 되어야 할것이다.
      • 기능 테스는 종종 매우 구체적인 내용들을 테스트 하기도 한다.
      • 아직은 통괄적인 수락 테스팅 툴은 나오지 않았고 Junit은 어떤 자바클래스에서도 수행될 수 있으나
      • 수락 테스팅 도구는 특정 애플리케이션 요구에 따라 작성되어야 한다.
      • HttpUnit을 이용하면 테스팅 API를 이용하여 웹 리소스에 대한 호출과 응답 값 조회를 프로그래밍 할 수 있도록 한다.
        **부하테스트 : JMeter, JUnitPerf등
        **인수테스트 : .

About Junit

기존에 TDD의 개념은 있었으나 도와주는 도구의 부재로 자바 개발자들은 주로 main() 에서 테스트 하는 방식을 주로 이용했을 것이다.
먼저 JUnit이 무엇이고 테스트란 무엇인지 알아보자.
테스트 코드는 작성시에 일반적으로 권장되는 몇가지 사항이 있다.

  • 이름짓기 규칙

    test 로 시작하는 메서드 이름으로 작성한다.
    즉, createMethod() 라는 메서드를 테스트 하려면 testCreateMethod()로 만드는 것이 좋다.
    그리고 eclipse의 junit 플러그인은 test로 시작된 메서드들을 찾아서 리플렉션을 통해서 테스트를 수행하게 한다.

  • 테스트 코드는 이런 작업들을 수행하도록 설정한다.

    테스트에 필요한 모든 조건과 상황을 준비 설정한다.
    테스트 대상이 되는 메서드를 호출한다.
    테스트 대상이 되는 메서드가 원하는 대로 동작한다는 것을 검증한다.
    실행이 끝나고 다른 코드에 영향이 없게 정리작업을 한다.

  1. JUnit 이란 독립된 테스트를 할 수 있도록 도와주는 framework이다.
  2. JUnit은 웹에서 무료로 다운로드할 수 있다. 설치는 classpath 환경 변수에 추가해주면 완료.
  • 우리는 이클립스 환경에서 사용할 것이므로 별다른 설치 없이 이클립스에서 제공하는 기능을 사용한다.
  • 이클립스의 Junit화면

    Failures 탭은 실패한 테스트의 목록을 나열한다.
    Hierarchy 탭은 실행된 모든 테스트의 전반적인 구조를 보여주는데 이것은 test suite(테스트 묶음) 을 실행할때 특히 유용하다.

  • JUnit의 단정메서드들 ( assert )

    *assertEquals - 같은지 비교
    *assertNull - null값을 리턴하는지 비교
    *assertNotNull - 인자로 넘겨받은 객체가 null인지 판정하고 반대인경우 실패로 처리한다.
    *assertSame - assertSame 은 expected 와 actual이 같은 객체를 참조하는지 판정하고 그렇지 않다면 실패로 처리한다.
    *assertNotSame - expected 와 actual이 서로 '다른' 객체를 참조하는지 판정하고, 만약 같은 객체를 참조한다면 실패로 처리한다.
    *assertTrue - boolean 조건이 참인지 판정한다. 만약 조건이 거짓이라면 실패로 처리한다.
    **assertTrue가 여기저기 들어가 있거나
    **테스트 코드라고 작성한 코드라고 주욱 작성해 놓고 마지막에 이 한줄을 넣어두는 코드는 의미가 없다.
    *fail - 테스트를 바로 실패 처리한다.
    *참고 : JUnit Quick Reference.pdf

  • 이 assert 메서드들은 테스트 대상이 되는 메서드의 검증을 위해 테스트 메서드 하나에는 assert 메서드가 여러개 들어간다.
    assert 메서드가 하나라도 실패하면 테스트 메서드는 중단되고, 나머지 assert 메서드들은 실행되지 않는다.
  • JUnit프레임워크
    1. import문으로 junit클래스들을 참조
    2. 테스트를 포함한 클래스는 모두 TestCase를 상속해야 한다.
    3. 테스트 클래스는 각 test.... 메서드를 정의한다.
    4. 모든 test..... 메서드는 Junit에 의해 자동으로 실행된다.
  • Junit 테스트 조합
    • test suite가 이것을 가능하게 하는데 모든 테스트 클래스는 suite라는 이름의 정적 메서드를 가질 수 있다.
    • public static Test suite();
    • 원하는 테스트 묶음을 반환하는 suite() 메서드를 작성하면 된다.
      ( 이 suite() 메서드가 없으면 JUnit은 모든 test.... 메서드를 자동으로 실행한다. )
  • 테스트별 준비 설정과 정리
    • setUp() 메서드는 각 test..... 메서드들이 실행되기전에 호출된다.
    • tearDown() 메서드는 각각의 테스트 메서드들이 실행되고 난 다음에 호출된다.

      테스트 클래스에 public void setUp() 메소드를 만들어주면 각 테스트 메소드가 실행되기 전에 먼저 setUp() 메소드가 실행된다.
      테스트 클래스에 public void tearDown() 메소드를 만들어주면 각 테스트 메소드가 종료할 때마다 tearDown() 메소드가 실행된다.

// 이 코드는 간단한 프레임워크및 실행순서를 보여준다. 
oneTimeSetup(){} //- 한번만 실행되는 스위트를 시작할 때의 준비설정코드와 끝날때의 정리코드
setUp(){} //- 메서드별로 테스트 메서드 이전에 실행되는 준비설정코드와, 이후에 실행되는 정리코드
testMethod(){}
tearDown(){}
setUp(){}
testMethod2(){}
tearDown(){}
oneTimeTearDown(){}

예를들어, 테스트마다 db연결 객체가 필요하다고 가정할때,
데이터베이스에 연결하고 접속을 종료하는 코드를 각 테스트 메서드에 일일이 넣을 필요 없이
setUp()과 tearDown() 메서드를 이용하여 해결하면 될것이다.

ex)

public Class TestDb extends TestCase()
{
private Connection dbConn;


protected void setUp()
{
dbConn = new Connection( "oracle", 1521, "scott", "tiger");
dbConn.connect();
}
	protected void tearDown()
{
dbConn.disconnect();
dbConn = null;
}
	public void testEmpAccess()
{
//dbConn 사용 어쩌고 저쩌고....
}
	public void testDeptAccess()
{
//dbConn 사용 어쩌고 저쩌고....
}
}
  • 이 간단한 예제에서는 setUp이 호출된 다음에 testEmpAccess() 가 호출이 되고 tearDown() 이 호출된다.
  • 그리고 setUp() 이 다시 호출되고 testEmpAccess() 가 실행되고 난 후에 tearDown()이 다시 호출된다.

대개는 이렇게 테스트별 준비 설정만으로 충분하지만
어떤 상황에서는 "전체" 테스트 스위트를 실행하기위해 어떤것을 설정하거나 정리해야 할 필요가 있다.
이런 경우 스위트별 준비 설정과 정리가 필요한데 이 설정은 조금 더 복잡하다.
필요한 테스트들의 스위트를 반들어서 TestSetup 객체 안에 감싸 넣어야 한다.

public static Test suite() 
{
TestSuite suite = new TestSuite();




// Only include short tests
suite.addTest(new TestClassTwo("testShortTest"));
suite.addTest(new TestClassTwo("testAnotherShortTest"));
	junit.extensions.TestSetup wrapper = junit.extensions.TestSetup(suite) 
{
// TestSetup 클래스의 setUp과 tearDown() 메서드를 재정의 한다.
protected void setUp() { oneTimeSetUp(); }
protected void tearDown() { oneTimeTearDown(); }
};
	return wrapper; 
}
public static void oneTimeSetUp() 
{
// 한번만 실행되는 초기화 코드가 여기에 들어간다.
tsp = new TSP();
tsp.loadCities("EasternSeaboard");
}
public static void oneTimeTearDown() 
{
// one-time cleanup code goes here...
tsp.releaseCities();
}
  • JUnit에서 제공하는 asssert 메서드만 사용하는 것보다 직접 사용자 정의 클래스를 만들어 상속하여 사용하는 것이 좋다.
  • JUnit과 예외
    1. 테스트에서 발생하는 예상된 예외
    2. 뭔가 크게 잘못 되어서 발생하는, 예상하지 못한 예외
      우리가 일반적으로 생각하는 것과는 달리 예외는 무언가 잘못되었다는 것을 알려주는 굉장히 유용한 도구다.
      가끔 테스트에서는 테스트 대상이 되는 메서드가 예외를 발생시키기를 '바라는' 경우도 있다.
      sortList()라는 메서드를 생각해보자. 이 메서드는 빈 목록을 받았을때 예외를 발생시켜야 한다.
       
public void testForException()
{
try
{
sortList(null);
fail("Should have trown an exception");
}
catch (RuntimeException e)
{
assertTrue(ture);
}
}

이 테스트 메서드의 경우 sortList를 테스트 할때 null이 넘어가면 예외를 발생시키는게 맞는 제어흐름이므로
catch절에 assertTrue(ture); 가 수행이 된다.
즉, 이는 나중에라도 코드를 잘못 해석할 가능성을 막아주는 강력한 문서화 기법이다.
다만 assertTrue(ture); 가 호출되지 않는다 하더라도 테스트가 실패하는 것은 아니라는 것은 명심하라.
그렇다면 예상하지 못한 예외들은?
사용자가 직접 처리하는 메서드를 구현할 수 있겠으나
메서드 선언부에 throws 절을 명시해 준다면 JUnit 프레임워크가 발생한 모든 예외를 잡아서 실패한 assert메서드를 출력하며
버그에 이르기까지 "전체" 호출 스택을 출력해 주므로 무엇때문인지 알아볼 때 많은 도움이 된다.

  • Java 의 JUnit은 test...로 시작하는 메서드가 자동으로 테스트 메서드로 인식되어 실행된다.

이것은 ,, 실제 테스트할 준비가 될때까지 메서드의 이름을 test로 시작하는 메서드가 아니라면 실행을 하지 않게 할수 있다는 것을 의미한다.
다만, 익숙해져선 안되는 것은 바로 테스트가 실패하는데도 이를 무시하는 습관이다.

  • JUnit의 테스트 골격 - JUnit테스트 사용에서 정말 필요한것은
    [{ImportantPlugin
  • junit.framework.* 를 반영하기위한 import 문
  • 테스트 클래스가 TestCase를 상속하게 하는 extends 문
  • supert(String)를 호출해주는 생성자
    많은 ide가 최소한 이만큼의 기능은 제공한다.

! 무엇을 테스트 할것인가?

  • 결과가 옳은가? - 당연한 말이다.

    또하나, 많은 양의 테스트 데이타가 필요한 테스트를 한다면, 단위 테스트에서 읽어들이는 별도의 데이타 파일에 테스트 값이나 결과를 넣는 것을 고려하는 것이 좋다.

  • 경계조건 - 대부분의 버그는 보통 '경계'에 서식한다.

    형식일치, 순서, 범위, 참조, 존재성, 개체수, 시간 등을 테스트한다.

    • 형식일치: 이메일, 전화번호, 주민번호등의 간단한 형식부터 레코드에 여러개의 레코드가 연결된 등의 형식 검증
    • 순서 : 정렬등의 루틴 등의 검증
    • 범위 : 애플리케이션 영역의 제약에 따른 범위
    • 물리적인 데이터 구조에 따라 제한되는 범위 - 배열이나 스택등의 인덱스 범위등
    • 참조
      **메서드가 자기 영역을 벗어난 어떤 것들을 참조하는가?
      **외부 의존성이 있는가?
      **클래스가 가져야 하는 상태는?
      **그 메서드가 제대로 동작하려면 그 밖에 어떤 조건을 가져야 하는가?

    예를 들면, 해당 메서드가 동작하기 위한 사전 조건( pre condition ) 이나 사후 조건( post condition ) 은
    어떤 상태여야 하는가? 등의 것들에 대한 것들인데.
    스택의 pop() 메서드처럼 비어있지 않은 스택을 필요로 한다던지
    고객의 거래 내용을 보여주는 어플리케이션에서는 고객의 로그인이 선행 되는 것을 필요로 한다던지
    dbConnection을 끊기 위해서는 먼저 dbConnetion 이 먼저 맺어져 있어야 한다던지 하는 것을

    • 존재성 : 주어진 것이 존재하는가.
      즉, null 이거나 비었거나 0이라면 그 메서드에 어떤 일이 일어날 것인가?
      메서드가 無를 확실히 이겨낼 수 있게 해야 한다.
    • 개체수
      테스트는 경계조건 0,1,n(한개 이상이면 n으로 논리적으로 취급된다.) 에 집중하게 된다.
      n은 업무상 필요에 따라 변하게 될 것이다.
    • 시간
      상대시간(시간적 순서)
      절대시간 ( 경과한 총 계산 시간)
      동시성 문제
  • 역관계 확인 - 논리적 inverse를 적용하여 검증해 볼 수 있다.
  • 다른 수단을 이용한 교차확인
  • 에러조건을 강제로 만들어내기
  • 성능특성

모의객체 사용하기 - 단위 테스트의 목표는 한번에 메서드 하나를 테스트 하는 것이다. 하지만, 테스트 하기가 어려운 상황이라면?

  1. 간단한 스텁 - 설계를 테스트 하는 동안 코드에서 임시로 만들어 사용한다.
    예)
    public long getTime()
    {
    if( debug )
    {
    return debug_cur_time;
    }
    else
    {
    return System.currentTimeMillis();
    }
    }

    하지만 우리에게 필요한것은 똑같은 일을 해내면서 더 깔끔하고, 더 객체지향 적인 방법이다.

  2. 모의 객체

    진짜 객체가 비결정적인 동작을 한다.( 예상할 수 없는 결과를 만들어 낸다.)
    진짜 객체를 준비 설정하기 어렵다.
    진짜 객체가 직접 유발시키기 어려운 동작을 한다.
    진짜 객체가 느리다.
    진짜 객체가 사용자 인터페이스를 가지거나, 사용자 인터페이스 자체다.
    테스트가 진짜 객체에게 그것이 어떻게 사용되었는지 물어보아야 한다.
    진짜 객체가 아직 존재하지 않는다.( 다른팀이나 새로운 하드웨어 시스템과 함께 일할 때 흔한 문제다.)

    모의 객체를 사용하기 위한 세단계
    *객체를 설명하기 위해 인터페이스를 사용한다.
    *제품 코드에 맞게 그 인터페이스를 구현한다.
    *테스트에 쓸 모의 객체의 인터페이스를 구현한다.

참고자료
http://sourceforge.net/projects/mockobjects
http://www.easymock.org

모의 객체의 사용법의 핵심은 각자가 흉내내는 뭔가에 의존하는 객체를 테스트 하기 위한것이다.

  • 좋은 테스트의 특징
  1. 자동적 : 테스트를 실행하는 경우와 결과를 확인하는 경우를 모두 의미하는 것.
  2. 철저함 - 문제가 될 수 있는 것은 모두 테스트한다.
  3. 반복가능
  4. 독립적
  5. 다른 테스트로부터도 독립적, 환경, 다른 개발자 로부터도 독립적
  6. 전문적 : 전문적 표준을 유지하면서 작성되어야 한다.

    테스트를 테스트 하기위한 방법의 일환으로
    일부러 버그를 집어넣어 테스트를 검증하기도 한다.
    그리고 코드를 고쳐서 테스트를 통과하게 하면 된다.

  • 프로젝트에서 코드의 구조화의 방법
    • 같은 디렉터리
    • 가장 쉬운 방법
    • 단점은 제품 코드와 테스트 코드가 같이 섞여 어질러져 있다는 것
    • 하위 디렉토리
    • 이방법은 테스트 코드를 적어도 제품 코드와 똑같은 디렉터리에 있지는 않게 떨어뜨려 놓을 수 있다는 이점이 있다.
    • test라는 하위의 패키지에 위치하게 되는 테스트 코드를 위해 가시성이 protected 같은 경우, 단지 테스트만을 위한 이 제품코드를 상속바받아서 정보를 노출하는 하위 클래스를 작성해야 한다.
    • 병렬트리
    • test 클래스를 제품 코드와 같은 패키지 안에, 그러나 다른 소스 코드 트리상의 위치에 넣는 것이다.즉, 컴파일러의 classpath안에 두개의 서로 다른 위치를 위치시켜서 인식을 시키는 것이다.
  • 팀작업에서 소스 공유시 테스트 예절은 말 하지 않아도 중요하다는 것에 모두 동의 하실 것이다.

    불완전한 코드(의존성 있는 코드가 체크인 되지 않은.)
    컴파일되지 않은 코드
    컴파일 되긴 하지만, 다른 코드를 망가뜨려서 컴파일 되지 않게 만드는 코드
    대응하는 단위 테스트가 없는 코드
    단위 테스트가 실패하는 코드
    자신의 테스트는 통과하지만, 시스템의 다른 테스트를 실패하게 만드는 코드

  • 테스트빈도의 종류

    새 메서드를 작성할 때마다: 지역단위 테스트들을 컴파일 하고 실행한다.
    버그를 고칠 때마다 : 버그를 드러내는 코드를 실행한다.버그를 고치고 단위테스트를 다시 실행한다.
    성공적으로 컴파일할 때마다 : 지역 단위 테스트들을 실행한다.
    버전 관리 시스템에 체크인할 때마다 : 모든 모듈 또는 시스템의 단위테스트들을 실행한다.
    끊임없이 : 따로 배정된 특정 컴퓨터가 자동으로 하루종일( 주기적으로 , 또는 버전 관리 시스템에 체크인 할때마다)처음부터 전체 빌드와 테스트를 수행하고 있어야 한다.

    Using JUnit Simple Demo

    먼저 테스트 해 볼 것은 간단한 예제로 주어진 배열중 가장 큰수를 리턴하는 메서드를 작성하는 테스트를 작성해 보기로 한다.
    그리고 TestSuite의 사용법을 위해 TestCase를 더 작성후 TestSuite를 작성해본다.

Make TestCases













Make TestSuite






논외

*TDD는 다음과 같은 순서로 진행을 하면 된다. 이 순서는 켄트벡이 제시한 순서이다.
1. Quickly add a test(테스트 프로그램을 작성한다.)
2. Run all tests and see the new one fail
(모든 테스트 프로그램을 수행시키고 테스트에 실패한 부분을 확인한다.)
3. Make a little change(소스를 추가하거나 변경한다.)
4. Run all tests and see them all succeed
(다시 모든 테스트를 수행시키고 모두 테스트를 통과했는데 확인한다.)
5. Refactor to remove duplication(중복을 제거하기 위해 Refactoring 한다.)


*이클립스에서 TestCase자동생성