본문 바로가기

Dev.../웹서비스

[펌] Test Driven Development & Eclipse - IV

TDD를 이용한 게시판 프로그램

 

실제 게시판 프로그램을 만들어 보면서 TDD를 진행해 보자. 게시판 프로그램을 진행하기 위해 다음과 같은 테이블을 구성한다. 여기서는 간단하게 하기 위해 답변형 게시판보다는 일반형 게시판을 만들어 보자.

 

게시판 테이블 생성 스크립트(mySQL)

CREATE TABLE TEST_BOARD (

    BOARD_ID      INT          NOT NULL,          /* 게시글 ID(일련번호)          */

    TITLE         VARCHAR(255) ,                  /* 제목                         */

    CONTENTS      TEXT         ,                  /* 내용                         */

    WRITE_NAME    VARCHAR( 20) ,                  /* 사용자성명                   */

    PRIMARY KEY(BOARD_ID)

)

 

TDD는 테스트 프로그램을 먼저 만들기 때문에 게시판 프로그램을 작성하기 전에 어떻게 테스트를 실행할 것인지에 대해서 생각해보자.(XP-eXtreamming Program에서는 이러한 테스트 시나리오의 수립을 고객이 하도록 하고 있다. 고객이 자신이 구축될 시스템에 대해 고객이 실제로 필요로 하는 기능이 구현되었는지에 대한 검증을 하는 것이기 때문에 개발자들이 대신해 줄 수는 없는 것이기 때문이다. 개발자는 이 테스트 사항을 프로그램으로 자동화하고 이 테스트를 통과하도록 프로그램을 개발하면 된다.

게시판의 전체 테스트 시나리오 구성은 다음과 같을 것이다.

 

사용자 Action

기능

테스트 사항

게시판 메뉴 클릭

게시판 이동

특정 게시판 메뉴 클릭 시 해당 게시판의 목록 조회화면으로 이동 여부

 

게시판 목록 조회

해당 게시판의 목록이 화면에 나타나는지 여부

(전체 게시글의 건수, 페이지수, 1페이지에 10개의 게시글 조회-번호, 제목, 작성자)

Write 버튼 클릭

게시글 등록 화면

게시글 등록화면으로 이동 여부

게시글 작성 후 저장 버튼 클릭

게시글 등록

사용자가 입력한 게시글이 등록되고 게시판 목록 조회화면으로 이동하고 등록된 글이 게시판에 나타난다.

목록에서 특정 게시물을 선택한 후 클릭한다.

게시글 상세조회

선택된 게시글의 세부사항을 조회하여 상세조회 화면에 나타내는지 여부

상세조회한 화면에서 삭제버튼 클릭

게시글 삭제

선택된 게시글이 삭제되고 목록조회 화면으로 이동한다.

 

위의 시나리오에서 각 항목은 별도의 프로그램의 단위 테스트에 해당하고 각 항목을 통합하여 하나의 테스트를 수행하는 경우 통합테스트가 된다. 여기서 제시하는 예제는 각각의 기능을 구현하기 위해 각 단위 테스트에 해당하는 테스트 프로그램을 만들고 이들을 통합하여 하나의 테스트를 수행하는 테스트 프로그램을 만들었다.

이중에서 화면의 이동과 관련된 부분의 View Layer에 해당하는 부분이기 때문에 이 부분은 뒤에서 cactus를 이용하여 테스트 하기로 하고 먼저 시스템의 Business Layer 부분을 테스트 해보기로 하자. 앞에서 설명했듯이 테스트 프로그램은 각각의 Layer 별로 테스트 프로그램을 작성한다고 했다.

 

☞ 게시판 프로그램 상세조회 개발 및 테스트

 

개발 순서는 상세조회, 삭제, 등록, 기타 검색 기능 순서로 개발하였다. 이유는 다른 수정, 조회와 같은 기능을 테스트하기 위해서는 테스트용 데이터가 등록되어야 하고 테스트가 완료된 후에는 테스트용으로 입력된 데이터는 삭제되어야 하기 때문이다. 또한 등록 기능을 테스트하기 위해서는 DB에 정상적으로 등록되었는지 확인하기 위해서 등록된 데이터를 조회하여 등록시킨 값과 조회된 값이 일치하는지 검증을 해보아야 하기 때문이다.

(켄트벡은 각각의 Test들간에 dependency가 없어야 한다고 말하고 있다. 이 말의 의미는 필자의 경우처럼 각각의 테스트 프로그램들간의 의존성 없이 독립적으로 테스트 수행이 가능해야 한다는 것을 의미한다. 여기에서 제시하는 예제의 경우 상세조회 테스트가 실패하는 경우 입력 기능에 대한 테스트는 할 수 없게 된다.

필자가 이렇게 구성한 이유는 대부분의 프로젝트에서는 DB에 정보를 등록하고 조회하는 기능이 많은데 등록/수정/삭제 등의 기능을 테스트하기 위해서는 어쩔 수 없이 DB의 데이터를 조회하는 기능과 의존성을 가질 수 밖에 없었기 때문이다.

DB의 실제 정보를 조회하는 공통 기능을 만든 다음 이 기능을 이용하여 SQL문을 직접 이용하여 데이터를 조회한 다음 검증하는 방법도 가능할 것이다. 다른 방법에 대해 알거나 다른 방안이 있으면 필자에게도 알려주기 바란다.)

물론 상세 조회 기능을 테스트하기 위해서도 테스트 데이터가 필요한데 이때에는 개발자가 직접 DB에 값을 입력하고 테스트해보는 수밖에 없다(아니면 Mock Object를 이용할 수 도 있다).

 

 

먼저 앞의 eclipse 설치 시 수행한 Tomcat 프로젝트를 생성한다. 예제의 경우 프로젝트 명은 tdd로 하였다. 패키지 구성은 테스트 클래스가 위치하는 패키지는 test 라는 별도의 패키지를 만들고 이 패키지 아래로 각각의 서브 시스템에 해당하는 패키지를 만들어 해당 서브 시스템의 테스트 클래스를 위치시킨다.

 

 

위의 화면에서는 게시판(board), 사용자 관리(user) 두개의 서브 시스템이 존재하고 각각의 기능에 대한 테스트용으로 tdd.test.board, tdd.test.user 패키지를 만들었다.

 

앞에서는 JUnit에서 제공하는 도구를 사용하였는데 매번 클래스를 수정하고 다시 테스트 도구로 이동하여 테스트를 수행하는 것 역시 번거로운 작업이다. 앞장에서 열심히 eclipse에 대해 설명한 이유가 이러한 테스트 사항도 eclipse 상에서 수행하기 위해서다. 이제 eclipse를 이용하여 테스트 프로그램을 수행시켜 보자.

Eclipse 상에서 프로젝트를 생성한 다음 클래스 패스 설정화면 앞에서 다운로드 받은 junit.jar 파일을 추가해준다.

 

 

추가로 SQL관련 처리를 위해 사용하고 있는 DBMS의 JDBC드라이버는 WEB-INF/lib 폴더로 import 하고 build path에 추가한다. (import 방법은 2장에서 설명).

 

테스트 클래스의 명명규칙은 Test라고 postfix를 사용한다. 게시판 상세조회 기능에 대한 테스트 클래스는 BoardFindByKeyTest가 된다.

클래스를 생성하기 위해 New → Class를 선택하여 Super Class 에 TestCase를 추가해준다.

 

 

먼저 BoardFindByKeyTest 클래스에서 테스트를 수행할 메소드를 작성한다. 메소드 명은 testBoardFIndByKey()로 한다.

게시판의 모든 기능은 BoardService 클래스가 담당하도록 구성을 할 것이기 때문에 다음과 같이 테스트 프로그램이 작성될 것이다.

 

BoardFindByKeyTest.java

package tdd.test.board;

 

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

import tdd.board.Board;

import tdd.board.BoardService;

import tdd.common.DomainKey;

 

public class BoardFindByKeyTest extends TestCase

{

public void testBoardFindByKey() throws Exception

{ 

       DomainKey key = new DomainKey(new Integer(1234));   

       BoardService service = new BoardService();          

       Board board = service.findBoardByKey(key);                        

       assertNotNull(board);           

       assertEquals(board.getKey(), key);           

       assertEquals(board.getTitle(), "TEST_TITLE");       

       assertEquals(board.getContents(), "TEST_CONTENTS");        

       assertEquals(board.getWriteName(), "TEST_USER_NAME");      

}               

 

    public BoardFindByKeyTest(String name)

    {

super(name);

    }

public static Test suite()

{

       return new TestSuite(BoardFindByKeyTest.class);

}

public static void main(String[] args)

{

     junit.textui.TestRunner.run(suite());

}        

}

 

여기까지 작성하면 컴파일 에러가 발생하게 된다. 지금까지 작성된 클래스는 아무것도 없기 때문에 Board, BoardService와 같은 클래스를 만들어야만 최소한 컴파일이라도 가능하다.

이제 Board, BoardService 클래스를 만들어 보자.

 

Board.java

public class Board extends DomainObject

{

String title;

       String contents;

       String writeName;

 

       getter…

        setter…

}

 

BoardService.java

public class BoardService

{

       public Board findBoardByKey(DomainKey key)

       {

             return null;

       }

}

 

(DomainKey, Board 클래스는 첨부 참고)

 

Board, BoardService 클래스를 만들면 이제 BoardFindByKey 클래스를 컴파일하면 에러는 발생하지 않는다. 컴파일은 통과하였기 때문에 이제 테스트 프로그램을 실행시켜 본다.

 

JUnit 결과는 당연히 에러일 것이다. 에러가 발생한 부분은 검색한 Board 객체의 NULL 여부를 확인하는 다음 부분에서 테스트를 통과하지 못했다.

 

           assertNotNull(board);

 

 

BoardService에서 findBoardByKey() 메소드에서 null 값을 반환했기 때문에 당연히 NotNull 체크에서 테스트를 통과하지 못했을 것이다.

이제 컴파일은 통과하지만 실제 구현해야 할 기능이 테스트를 통과하지 못했기 때문에 테스트를 통과하도록 기능을 구현해보자.

BoardService의 findBoardByKey() 메소드는 파라미터로 받은 Key 값에 해당하는 게시판 정보를 DB에서 load하여 Board 객체를 생성한 다음 반환하는 기능을 수행할 것이다. 따라서 소스는 다음과 같이 될 것이다(전체소스는 별첨 참고).

 

BoardService.java

public class BoardService

{

       public Board findBoardByKey(DomainKey key) throws Exception

       {

             return (Board)Board.findByKey(key);

       }

}

 

위의 BoardService 역시 컴파일 시 에러가 발생할 것이다. Board 클래스에 findByKey() 라는 메소드가 없기 때문이다. Board에 다음과 같이 findByKey() 메소드를 추가하고 다시 컴파일 해 본다.

 

Board.java

public class Board

{

//멤버변수