반응형





http://www.javajigi.net/display/OSS/TDD



TDD

Toggle Space Navigation Tree
Space Map

TDD와 JUnit을 이용한 테스팅 방법을 알아보고 효율적인 테스팅 방법에 대해 토의해봅시다.

오픈 소스 스터디

Table of Contents

Introduction

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

TDD

자동화된 테스트로 개발을 이끌어 가는 개발 방식을 테스트 주도 개발이라 부른다. 
TDD는 분석 기술이며, 설계 기술이며, 개발의 모든 활동을 구조화하는 기술이다. 
작동하는 깔끔한 코드(clean code that works). 
이 핵심을 찌르는 한마디가 바로 테스트 주도 개발의 궁극적인 목표다.

테스트 주도 개발에서는 두 가지 단순한 규칙만을 따른다.

  • 오직 자동화된 테스트가 실패할 경우에만 새로운 코드를 작성한다.
  • 중복을 제거한다.

또한 위의 두 가지 규칙에 의해 프로그래밍 순서가 다음과 같이 결정 된다.

  • 빨강- 실패하는 작은 테스트를 작성한다. 처음에는 컴파일조차 되지 않을 수 있다.
  • 초록- 빨리 테스트가 통과하게끔 만든다. 이를 위해 어떤 죄악을 저질러도 좋다.
    (죄악이란 기존 코드 복사해서 붙이기-copy and paste, 테스트만 간신히 통과할 수 있게끔 
    함수가 무조건 특정상수를 반환하도록 구현하기 등을 의미한다.)
  • 리팩토링- 일단 테스트를 통과하게만 하는 와중에 생겨난 모든 중복들을 제거한다

TDD 따라해보기

  • MyProject 라는 프로젝트를 만들었다. 메뉴> New> Project
  • TestCase를 생성한다. 이클립스에서는 TestCase와 TestSuite의 기본 템플릿을 제공하고 있다.
    메뉴> New> Other을 누르면 다음과 같은 창이 뜬다.

  • 생성할 TestCase 파일 이름 및 테스트 대상 클래스를 입력한다. (①)
    만약 setup() 메소드나 teardown() 메소드를 사용할 때는 해당 체크박스를 선택한다. (②)

  • 다음이 생성되었다.

  • 할일 목록을 작성해보자. 작업을 끝낸 항목에는 표시를 하고,
    또 다른 테스트가 생각나면 할일 목록에 새로운 항목을 추가할 것이다.

  • 자, 그럼 구현을 하기 위해 어떤 객체가 있어야 할까?
    방금 이건 속임수다. 객체를 만들면서 시작하는 게 아니라 테스트를 먼저 만들어야 한다. 
    앞에서 언급한 TDD의 주기중 현재 단계는 빨강이다.
  • 그렇다면 어떤 테스트가 필요할까? 할일 목록을 보니 첫번째 테스트는 좀 복잡해보인다.
    작은 것부터 시작하든지, 아니면 손을 대지 않는 게 좋겠다. 이번엔 다음 항목인 곱하기를 보자. 
    대단히 어렵진 않겠지? 이걸 먼저 하는게 좋겠다.
  • testMultiplication이라는 테스트를 추가해보자.

  • 다음 코드를 돌리면 당연히 에러가 난다. 메뉴> Run As> JUnit Test

  • 할일 목록에 3가지를 추가한다.

  • 빨간 막대기를 가능하면 빨리 초록 막대기로 바꾸고 싶다.
    실행은 안 되더라도 컴파일만은 되게 만들고 싶은데, 가장 쉬운 방법이 뭘까? 
    이번 단계는 TDD주기에서 빨강에서 초록으로 넘어가는 과정이다.
  • 현재 네 개의 컴파일 에러가 있다.
    • Dollar 클래스가 없음
    • 생성자가 없음
    • times(int) 메서드가 없음
    • amount 필드가 없음
  • 네개의 컴파일 에러를 없앨 수 있는 가장 빠른 방법은 다음을 테스트 코드 아래 추가하는 것이다.


  • 당장의 목표는 완벽한 해법을 구하는 것이 아니라 테스트를 통과하는 것일 뿐이므로 최소한의 작업을 수행한다.

  • 테스트를 다시 실행해보자.

  • 드디어 초록색 막대가 나타났다. 하지만 아직 주기가 완성되지 않았으니까 서둘지 않는게 좋겠다.
    우리는 TDD의 주기중 빨강/초록을 넘었다.
  • 다음 단계는 리팩토링이다.코드를 다음과 같이 바꿔준다.

  • 테스트는 통과하고 테스트 막대 역시 초록색이다. 우리는 여전히 행복하다.

  • 이 단계가 너무 작게 느껴지는가? 하지만 기억하기 바란다. TDD의 핵심은 이런 작은 단계를
    밟아야 한다는 것이 아니라, 이런 작은 단계를 밟을 수 있는 능력을 갖추어야 한다는 것이다.
  • 5를 어디서 얻을 수 있을까? 이건 생성자에서 넘어오는 값이니 이걸 다음과 같이 amount 변수에 저장하면,

  • 그걸 time()에서 사용할 수 있다.
    인자 multiplier의 값이 2이므로, 상수를 이 인자로 대체할 수 있다.

  • 마지막으로 우리가 자바 문법을 완벽하게 알고 있다는 것을 보여주기 위해 *=연산자를 써주자.( 물론 중복을 제거하기 위해서다. )

  • 이제 첫 번째 테스트에 완료표시를 할 수 있게 되었다.

  • 마무리 - 우리는 다음과 같은 작업들을 해냈다.
    • 우리가 알고 있는 작업해야 할 테스트 목록을 만들었다.
    • 오퍼레이션이 외부에서 어떻게 보이길 원하는지 말해주는 이야기를 코드로 표현했다.
    • JUnit에 대한 상세 사항들은 잠시 무시하기로 했다.
    • 스텁 구현을 통해 테스트를 컴파일했다.
    • 끔찍한 죄악을 범하여 테스트를 통과시켰다.
    • 돌아가는 코드에서 상수를 변수로 변경하여 점진적으로 일반화했다.
    • 새로운 할 일들을 한번에 처리하는 대신 할일 목록에 추가하고 넘어갔다.

TDD의 특징

  • 격리된 테스트
  • 테스트 목록
  • 테스트 우선
  • 단언 우선
  • 명백한 데이터

TDD와 관련된 의문

  • 단계가 얼마나 커야 하나?
  • 테스트 할 필요가 없는 것은 무엇인가?
  • 좋은 테스트를 갖췄는지의 여부를 어떻게 알 수 있는가?
  • TDD로 프레임워크를 만들려면 어떻게 해야하나?
  • 피드백이 얼마나 필요한가?
  • 테스트를 지워야 할 때는 언제인가?
  • 프로그래밍 언어나 환경이 TDD에 어떤 영향을 주는가?
  • 거대한 시스템을 개발할 때도 TDD를 할 수 있는가?
  • 애플리케이션 수준의 테스트로도 개발을 주도할 수 있는가?
  • 프로젝트 중반에 TDD를 도입하려면 어떻게 해야 할까?
  • TDD는 누구를 위한 것인가?
  • TDD는 초기 조건에 민감한가?
  • TDD와 패턴의 관계는?
  • 어째서 TDD가 잘 동작하는가?
  • TDD와 익스트림 프로그래밍의 실천법 사이에는 어떤 관련이 있는가?

JUnit

▪ 다운로드 : http://www.junit.org/

JUnit 기본 클래스

1. TestCase 클래스 : 가장 간단하게 Junit을 사용하는 방법은 TestCase 클래스를 상속받은 클래스를 작성하는 것이다. 이 클래스에는 test로 시작하는 메소드만 나열하면 된다.

2. TestSuite 클래스 : testCase 클래스를 상속받은 클래스만을 사용하다 보면, 일부의 test 메소드는 간혹 실행하지 않고 싶거나 특정한 test 메소드만 실행하고 싶을 때가 생긴다. (①) 또는 Test 클래스를 한데 묶어서 한꺼번에 실행하고 싶은 경우(②)도 발생한다. 이때 사용하는 것이 바로 TestSuite이다.

3. Assersions

4. Fixture : 초기 값 설정 및 해제 - setUp(), teardown()


JUnit4.0에서의 변경사항

  • Test Annotation
    이전 버전에서 사용했던 TestCase를 대신에 org.junit.Test를 임포트하여 다음과 같은 형식으로 사용한다.
    • @Test public void functionName() { }
  • Expected Exception
    @Test Annotation은 "expected" 라는 파라메터를 option 으로 가지고 있는데
    이것은 Throwable의 서브 클래스들을 값으로 취한다.
    • @Test(expected= IndexOutOfBoundsException.class) public void empty() {
      new ArrayList<Object>().get(0); 
      }
  • Timeout
    @Test Annotation은 long형 파라메터를 통해 timeout을 설정할 수 있다.
    • @Test(timeout=100) public void infinity()
      Unknown macro: { for ( ; ; ); }
  • Fixture
    • setup - Before Annotation
      • import org.junit.Before;
        @Before public void functionName()
    • tearDown - After Annotation
      • import org.junit.After
        @After public void functionName()
  • Ignore Annotation
    때때로 일시적으로 테스트를 작동시키지 않을 때가 있는데 Ignore이라는 Annotation을 사용할 수 있다. 
    Ignore하는 이유를 옵션 파라메터를 통해 기록할 수 있다.
    • @Ignore("not ready yet") @Test public void something()

단위 테스트 지침 요약

일반 원칙

  • 망가질 가능성이 있는 모든 것을 테스트한다.
  • 망가지는 모든 것을 테스트한다.
  • 새 코드는 무죄가 증명되기 전까지는 유죄.
  • 적어도 제품 코드만큼 테스트 코드를 작성한다.
  • 컴파일 할 때마다 지역 테스트를 실행한다.
  • 저장소에 체크인하기 전에 모든 테스트를 실행해 본다.

자문해 봐야 할 사항

  • 이 코드가 옳게 동작한다면 어떻게 그것을 알 수 있는가?
  • 이것을 어떻게 테스트할 것인가?
  • ''''' 어떤 것이 잘못될 수가 있는가?
  • 이와 똑같은 종류의 문제가 다른 곳에서도 일어날 수 있을까?

좋은 테스트란?

좋은 테스트는 'A- TRIP' 해야 한다.

  • 자동적('A'utomatic)
    • 테스트가 혼자 실행되고 자신을 검증할 수 있어야 한다.
  • 철저함('T'horough)
  • 반복 가능('R'epeatable)
    • 모든 테스트가 어떤 순서로든 여러 번 실행될 수 있어야 하고, 그때마다 늘 같은 결과를 내야 한다.
  • 독립적('I'ndependent)
    • 확실히 한 대상에 집중한 상태여야 한다.
    • 환경과 다른 개발자들에게서 독립적인 상태를 유지해야 한다.
    • 어느 테스트도 다른 테스트에 의존하지 않는다.
  • 전문적('P'rofessional)
    • 좋은 설계를 위한 모든 일반적인 규칙, 캡슐화 유지, DRY 원칙 지키기, 결합도 낮추기 등은 제품 코드에서 그랬듯이 테스트 코드에서도 반드시 지켜져야 한다,

무엇을 테스트 할 것인가?

Right-BICEP (오른쪽 이두박근이라는 뜻)

  • Right - 결과가 옳은가?
    예) 
  • B - 모든 경계('B'oundary) 조건이 correct 한가?
    <경계 조건에서 확인해봐야 할 사항들 - 'CORRECT'>
    • 형식 일치('C'onformance) - 값이 기대한 형식과 일치 하는가?
      예) 최상위 도메인 이름이 없는 이메일 주소 fred@foobar 이 넘어온다면?)
    • 순서('O'rdering) - 적절히 순서대로 되어 있거나 그렇지 않은 값인가?
      예)
    • 범위('R'ange) - 적당한 최소값과 최대값 사이에 있는 값인가?
      예) 
    • 참조('R'eference) - 코드가 자기가 직접 제어하지 않는 외부 코드를 참조하는가?
      예) 
    • 존재성('E'xistance) - 값이 존재 하는가?
      예) null이 아님, 0이 아님, 집합 안에 존재함)
    • 개체 수('C'ardinality) - 확실히 충분한 값이 존재하는가? 개수를 정확히 필요한 만큼 갖고 있다던가, 정확히 필요한 만큼 만들었다는 것을 확인해야 한다.
      예) 울타리 기둥 에러, 하나 차이에 의한 오류
    • 시간 ('T'ime) (절대적으로 그리고 상대적으로) - 모든 것이 순서대로 일어나는가? 제시간에? 때 맞추어?
      예) 로그인하기 전에 문서를 출력하려고 시도하는 것
  • I - 역('I'nverse) 관계를 확인할 수 있는가?
    예) 
  • C - 다른 수단을 사용해서 결과를 교차 확인('c'ross-check) 할 수 있는가?
    예) 
  • E - 에러조건('e'rror condition)을 강제로 만들어 낼 수 있는가?
    현실 세계에서는 에러가 발생한다. 여러분은 강제로 에러를 일으켜 코드가 현실 세계의 문제를 제대로 처리한다는 것을 테스트 할 수 있어야 한다. 
    네트워크 에러 등을 시뮬레이션 하는 경우 모의객체(mock object)를 사용할 수 있다.
  • P - 성능('P'erformance) 특성이 한계 내에 있는가?
    예) 

프로젝트에서 테스트하기

1. 테스트 코드를 어디에 둘 것인가?

  • 같은 디렉터리
    • 장점: TestAccont가 Account의 보호된 멤버 변수와 메서드에 접근할 수 있다.
    • 단점: 테스트 코드가 제품 코드 디렉터리의 공간을 잡아먹으면서 어질러져 있다.
  • 하위 디렉터리
    • 장점: 태스트 코드를 적어도 제품 코드와 똑 같은 디렉토리에 있지 않게 떨어뜨려 놓을 수 있다.
    • 단점: 테스트 코드가 다른 패키지에 있으므로 필요한 멤버들을 노출시키는 제품 코드의 하위 클래스를 사용하지 않는다면 테스트 코드는 보호된 멤버에 접근할 수 없다.
  • 병렬 트리
    • 장점: 테스트 코드와 제품 코드가 정말로 분리되었다. 테스트 코드가 같은 패키지에 있기 때문에 선택적으로 접근할 수 있다.
    • 단점: 편하게 쓰기에는 너무 멀리 떨어져버린 건지도 모른다.

2. 테스트 예절

  • 혼자하는 테스트와 다른 사람과 함께하는 테스트의 가장 큰 차이점은? 테스트 코드의 동기화
  • 팀 환경에서 코드를 체크인 할 때는, 그 코드가 단위 테스트를 완료하였고,모든 단위 테스트가 통과했다는 것을 확인해야한다.
    사실상, 전체 시스템의 코든 테스트가 새로운 코드에 대해 계속 통과해야 한다.
  • 잠재적 위반사항 목록을 만들자
    • 불완전한 코드(예: 클래스 파일 하나만 체크인 하고 그것이 의존할 수 있는 다른 파일은 체크인 하는 것을 잊어버린 경우)
    • 컴파일 되지 않는 코드
    • 컴파일 되기는 하지만, 다른 코드를 망가뜨려서 컴파일 되지 않게 만드는 코드
    • 대응하는 단위 테스트가 없는 경우
    • 단위테스트가 실패하는 코드
    • 자신의 테스트는 통과하지만, 시스템의 다른 테스트를 실패하게 만드는 코드

3. 테스트 빈도

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

4. 테스트와 레거시 코드

  • 이미 있던 코드의 경우 : 가장 망가진 부분을 위한 테스트를 가장 먼저 추가하는 편이 좋다.
    이런 환경에서 실시하는 단위 테스트는 뒷걸음 질을 막는다. 
    즉, 유지 보수하기 위해 수정하고 개선하다가 이미 있는 부분들에 버그를 만들어 내는 죽음의 나선을 피할 수 있다.
  • 연속적 빌드-테스트 프로그램을 사용하여 코드가 저장소에 전송될 때마다
    자동으로 단위테스트가 실행되게 만든다면 불량코드가 전체 회사에 돌아다니는 일을 방지할 수 있다.

5. 테스트와 검토

  • 코드 검토를 수행할 때, 그 검토과정의 필수 구성요소로 테스트 코드를 끼워넣어라
  • 코딩과 검토를 다음 순서로 하면 좋다
  • 테스트 케이스나 테스트 코드, 둘 다를 작성한다.
    1. 테스트 케이스나 테스트 코드, 또는 둘 다를 검토한다.
    2. 테스트 테이스나 테스트 코드, 또는 둘 다를 검토할 때마다 수정한다.
    3. 테스트를 통과하는 제품 코드를 작성한다.
    4. 제품코드와 테스트 코드를 검토한다.
    5. 검토할 때마다 테스트 코드와 제품코드를 수정한다.
    6. 모든 팀원을 이 과정에 참여시킴으로써 팀의 의사소통을 증진시킬 수 있다.
      다른 이들이 어떻게 테스트 하는지, 팀의 규칙이 무엇인지 알게된다.

설계와 관련된 문제들

1. 테스트의 용이성을 높이는 설계

2. 테스트를 위한 리팩토링

3. 클래스의 불변성(invariant)을 테스트 하기

  • 구조적
    • 주문 정보 입력 시스템은 다음과 같은 불변성을 가질 수 있다.
      • 모든 개별 항목은 어떤 주문에 속해야 한다.
      • 모든 주문은 개별 항목 하나 이상을 가져야 한다.
    • 데이터 배열을 다룰 때 인덱스 노릇을 하는 멤버 변수는 다음과 같은 불변성을 가질 수 있다.
      • 인덱스는 >=0이어야 한다.
      • 인덱스는 < 배열 길이여야 한다.
  • 수학적
    • 은행 계좌의 입금 부분과 출금 부분을 계산하면 잔고와 맞아 떨어져야 한다.
    • 다른 단위로 측정된 양은 변환 후에 맞아떨어져야 한다.
  • 데이터의 정합성(consistency)
    • 쇼핑 카트에 들은 항목 목록, 전체 판매액, 그 쇼핑 카트에 들은 전체 항목 개수는 밀접한 관련이 있다.
      항목 목록의 구체적인 내용을 알면, 다른 두 수치를 이끌어 낼 수 있다. 이 수치들이 조화를 이루어 맞아 
      떨어진다는 사실은 불변성이 되어야 한다.

참고문헌

  • 이클립스 기반 프로젝트 필수 유틸리티 CVS,Ant,JUnit (민진우 & 이인선 공저, 한빛미디어)
  • 테스트 주도 개발 (켄트 벡 저, 김창준 & 강규영 역,인사이트)
  • 실용주의 프로그래머를 위한 단위 테스트 with JUnit (데이비스 토머스 & 앤드류헌트 공저, 이용원 & 김정민 역, 인싸이트)
  • 참고 싸이트 - TDD를 공부하고 싶다면?
    http://wiki.tdd.or.kr/wiki.py
    http://c2.com/cgi/wiki?TestDrivenDevelopment
    http://xper.org/wiki/xp/

문서에 대하여

최초작성자 : 오혜진
최초작성일 : 2006년 2월 19일
버전 : 1.0 ? ㅎㅎ 
문서이력 :

  • 2005년 2월 19일 오혜진 문서 최초 생성
  • 2005년 2월 20일 JUnit4.0에서의 변경사항 추가
  • 2005년 2월 21일 오픈소스스터디 1기 장회수님 자료 참고하여 보완, 테스트의 종류 추가
  • 2005년 2월 24일 프로젝트에서 테스트하기 항목 추가 및 문서 구조 수정

반응형

+ Recent posts