Whiteship's Note


웹 통합 테스트 프레임워크 개발 중

모하니?/Coding : 2009.05.14 23:10


웹 통합 테스트를 지시 받고, 몇일 전 삽질부터 시작해서 오늘까지 조금 열심히 달렸습니다. 오늘은 오후 6시쯤 되니까 머리가 아파서 더 못 앉아 있겠더군요. 아침에 오자마자 만들기 시작해서 조금 하다 보면 오후 1, 2시가 금방 되고, 그러면 밥 먹고 나서 또 조금 하다 보면 4, 5시가 되고, 또 조금 하다 보면 7, 8시가 되니까 하루가 정말 짧더군요. @_@

아직도 많이 손봐야 하지만, 이제 조금 윤곽이 잡혔습니다.
@RunWith(WebTestRunner.class)
@WarConfiguration(appName="springsprout")
@DataConfiguration(dataType=DataType.XML, location="integration/sample/testData.xml")
public class IndexPageWebTest {

    @WebTest
    public void sample(){
        WebDriver driver = new HtmlUnitDriver();
        driver.navigate().to("http://localhost:8080/springsprout/index.do");
        assertTrue(driver.getTitle().equals("SpringSprout"));
    }

}

WebDriver를 이용한 초간단 웹 테스트 코드입니다. 이렇게 설정한 다음 테스트를 실행하면..
1. 현재 프로젝트를 WAR 패키징을 합니다.
2. 생성한 WAR를 테스트용 톰캣 서버에 springsprout 컨텍스트 패스로 배포합니다.(maven/tomcat6x/webapp/springsprout)
3. 배포가 잘 됐으면, 테스트 데이터를 넣어줍니다.
4. 이제 테스트를 실행합니다.
5. 테스트 데이터를 삭제합니다.
6. 앞서 배포한 WAR 파일을 undeploy 합니다.

여기서 4번에 해당하는 코드만 작성하면 됩니다. 나머지는 애노테이션만 붙여주면 되죠. 테스트 케이스 마다 서버를 켰다 껐다 하는 건 좀 무리고, WAR만 그때 그때 배포하도록 했습니다.

@RunWith(WebTestRunner.class)
@WarConfiguration(appName="springsprout")
@DataConfiguration(dataType=DataType.XML, location="integration/sample/testData.xml")
public class IndexPageWebTest {

    @WebTest
    public void test1(){
   
    }
   
    @WebTest
    public void test2(){
       
    }

}

이렇게 테스트를 하나 더 추가하면? WAR 배포 이후에 두 개의 테스트를 모두 실행한 다음에 WAR를 unploy합니다.

약간의 변화를 줄 수 있습니다. 테스트 데이터 입력이 필요 없다면, @DataConfiguration을 생략해도 됩니다. 그리고 테스트 서버 포트를 기본으로 8080을 사용하는데, 그 값을 @WarConfiguration의 port 속성에 줄 수 있습니다. 그럼 해당 포트에 배포를 시도하죠. 물론 그럴 때 해당 포트에 서버가 실행되고 있지 않다면, debug 모드의 로깅과 적절한 이름의 RuntimeException이 발생합니다.

꾸준히 가꿔야겠지만, 이제 내일 부터는 이녀석을 이용해서 본격적으로 웹 통합 CRUD 테스트를 만들고, CI를 해보려고 합니다.

얘 때문에 조금이라도 뒤적여 본 것들은 다음과 같습니다.
- JUnit 4.6
- Spring Test
- Cargo
- DBUnit
- WebDriver
- Maven Surefire Plugin
- Appfuse
- Maven Embedder

지난주 금, 월, 화, 수, 목.. 거의 일주일이네요.
개발이 참 더디고 어설프네요. ㅎㅎ 갈 길이 멀었습니다. @_@
top


Cargo의 TomcatManager

Good Tools : 2009.05.12 14:36


아파치 톰캣 매니저를 랩핑한 녀석인데 아주 편리합니다.

TomcatManager manager;
manager = new TomcatManager(new URL("http://localhost:8080/manager/"));
manager.deploy("/springsprout", new URL("file:target/springsprout.war"), true);
manager.undeploy("/springsprout");

이런식으로 사용할 수 있습니다.

이슈 1.

Cargo와 관련 된 것이 아니지만, 한 가지 이슈 사항은 undeploy를 했을 때 해당 webapp에서 path에 해당하는 폴더가 지워지지 않으면 그 다음에 같은 폴더로 deploy를 하면, 톰캣은 제대로 배포가 됐다고 생각을 하는데, 실제로는 그렇치 않습니다. 접속해보면 404가 뜨죠.

derby.jar 파일이 지워지지 않고 lib폴더에 남아있는 경우였는데, deploy할 때 무슨 이유에서든가 webapp 밑을 이 파일만은 유독 지워지지 않고 남아서 이 다음 deploy를 방해하고 있었습니다. @_@ 필요도 없는 라이브러리였는데 들어가서 말썽을 부리더군요.

이슈 2.

두 번째는 Cargo에서 제공하는 TomcatManager말고 여러 Deployer들이 있는데, 그 녀석들을 사용하여 deploy는 할 수 있었지만, 그 외의 메서드 redeploy(), undeploy() 등은 Not Supported 예외를 발생시킵니다. 메이븐으로 연동하였을 때도 마찬가지로 cargo:undeploy 등은 not support 에러가 나고 제대로 동작하지 않았습니다. 물론 제가 뭔가 잘못했을 수도 있습니다. @_@ 하지만, TomcatManager 설정이 Deployer 보다 더 간단하고 잘 동작하기 때문에 다시 Deployer를 시도해보진 않을 것 같습니다.


top


Cargo 메이븐 플러그인 설정하기

Good Tools : 2009.05.11 00:36


참조: http://cargo.codehaus.org/Maven2+plugin

상당히 간단하지만, 그 결과는 그다지 간단하지 않은 것 같습니다. 일단 설정은 이렇게 했습니다.

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>
                <configuration>
                    <container>
                        <containerId>tomcat6x</containerId>
                        <home>/apps/apache-tomcat-6.0.18</home>
                    </container>
                </configuration>
            </plugin>

이렇게 하고, cargo:start 골을 실행하면 톰캣 서버를 메이븐에서 실행할 수 있습니다. mvn cargo:start 이런식으로요.

테스트 진행은 다음 순서대로 할 겁니다.

WAR 파일 패키징 -> cargo를 사용하여 톰캣 서버 실행 -> 웹 (통합) 테스트 -> cargo를 사용한 톰캣 서버 정지

이 것을 그대로 메이븐 골로 옮겨 적으면 다음과 같습니다.

mvn package cargo:start test cargo:stop

그러나 이건 예상대로 동작하지 않습니다. 일단, 단위 테스트와 통합 테스트가 분리되어야 합니다. 단위 테스트를 실행하는데 굳이 카고를 이용하여 테스트 서버를 실행할 필요는 없기 때문이죠.

두 번째로 cargo:start를 실행하는 순간, 해당 커맨드 창은 이용할 수가 없습니다. 서버 로그가 뜨고 Ctrl+C를 눌러 서버 실행을 멈추기 전까지는 해당 커맨드 창이 먹통이나 다릅없습니다. 또 다른 콘솔을 띄우고 그 곳에서 테스트를 진행 한 다음 cargo:stop으로 서버 실행을 멈출 수 있습니다.

이 두 가지를 생각해 봤을 때 빌드가 2단으로 구성되어야 할 것 같습니다.

1단에서 할 일은 clean package(이 안에 complie, test 포함) cargo:start
2단에서 할 일은 integration-test, cargo:stop

궁금한 건 이러한 2단 빌드를 제공하는 CI 툴이 있느냐 하는 것과 메이븐으로 이런 구성을 하는 방법입니다. 후자는 금방 찾을 것 같은데 전자는 잘 모르겠네요. 기존 CI 툴의 plan을 잘 짜서 여러 plan끼리 의존성을 주며 실행하도록 할 수 있다면 가능할지도 모르겠고, 아예 하나의 plan으로 이러한 2단 빌드를 구성할 수 있게 해준다면 편리할 것 같습니다.
top


DBUnit + Cargo + Webdriver를 이용한 웹 테스트 삽질 중

모하니?/Coding : 2009.05.08 19:15


하려던 것은 간단합니다.

1. DBUnit으로 테스트 데이터를 넣고,
2. Cargo로 톰캣6를 돌리고
3. Webdriver로 HTML, IE, Firefox에서 CRUD+S(검색) 테스트를 하는 겁니다.

이게 되면 PageObject 패턴을 도입해서 테스트를 만들어 볼까 했는데, 아직 이 늪을 못 벗어나고 있습니다.

일단, Webdriver를 이용한 단독 테스트는 성공적이었습니다. 물론 아직 PageObject 패턴을 도입하진 않았었죠. 이 녀석이 해주는 일은 화면에 있는 정보를 쉽게 참조할 수 있게 도와주는 API를 제공해 주는 것입니다. 따라서 화면 테스트를 보다 쉽게 작성할 수 있곘죠. 그밖에 동일한 URI를 파이어폭스, 인터넷익스플로러, HTML, 사파리 드라이버를 이용하여 참조할 수 있어서 다기종 브라우저를 지원하는 자동화 테스트를 작성할 때 매우 유용할 것으로 보입니다.

Cargo는 서버를 조작할 수 있는 API를 제공하며, 여러 종류의 서버를 설정하고, WAR 파일을 배포하는 작업등을 할 수 있습니다. 자세히는 모릅니다. 매우 다양한 서버를 지원하며, 웹 테스트를 자동화 할 때 필수 도구로 보입니다. 또한 다양한 모드로 서버를 실행할 수가 있습니다.

DBUnit은 많이들 아실 것으로 생각하고 생략하겠습니다.

저는 먼저 Webdriver부터 실습해봤습니다. 홈페이지로 시작하기 같은 문서를 찾아서 살펴봤습니다. 그리고 바로 테스트를 작성해 봤습니다.

        WebDriver driver = new HtmlUnitDriver();
        driver.get("http://localhost:8080/springsprout/index.do");
        WebElement element = driver.findElement(By.linkText("Login"));
        assertNotNull(element);
        assertEquals("/login.do", element.getAttribute("href"));

음.. 잘 동작하네! API를 익혀야겠군...@_@ 이제 PageObject 패턴을 어떻게 도입해야 하나~

고민할 새도 없이 다음은 Cargo를 시작했습니다.

        Deployable war = new WAR("target/springsprout.war");
        LocalConfiguration configuration = new Tomcat6xStandaloneLocalConfiguration("target/springsprout");
        configuration.addDeployable(war);
        container = new Tomcat6xInstalledLocalContainer(configuration);
        container.setHome("c:/apps/apache-tomcat-6.0.18");
        container.start();
...
        container.stop();

이런식으로 서버를 동작 시킬 수 있었습니다. 저 ... 안에 위에 작성한 웹 드라이버를 넣어보니 잘 동작했습니다. 문제는 이렇게 서버를 매번 올리고 내리는 작업을 테스트케이스마다 하면 굉장히 테스트가 오래 걸리고 비효율적이라는 겁니다. 따라서 이 작업은 반드시 Cargo 메이븐 플러긴을 이용해서 모든 테스트를 실행하기 전에 테스트 서버를 올렸다가 내리도록 해야겠습니다. 아직 해보진 않았습니다 @_@

일단 또 다시 고민할 새 없이 바로 DBUnit 까지 적용해 봤습니다.

    private void insertXMLData() throws IOException, DataSetException, DatabaseUnitException, SQLException {
        InputStream sourceStream = new ClassPathResource("testData.xml", getClass()).getInputStream();
        IDataSet dataset = new FlatXmlDataSet(sourceStream);
        DatabaseOperation operation = DatabaseOperation.CLEAN_INSERT;
        operation.execute(new DatabaseConnection(DataSourceUtils.getConnection(dataSource)), dataset);
    }

이런 메서드를 이용해서 testData.xml에 만들어 둔 테스트 데이터를 DB에 넣고 Cargo로 서버를 돌리고 Webdriver로 화면에 출력된 데이터를 검증하면 되리라 생각했습니다.

    @Test
    public void listMember() throws Exception {
        insertXMLData();
        assertEquals(2, memberRepository.getMemberList().size());

        Deployable war = new WAR("target/springsprout.war");
        LocalConfiguration configuration = new Tomcat6xStandaloneLocalConfiguration("target/springsprout");
        configuration.addDeployable(war);
        container = new Tomcat6xInstalledLocalContainer(configuration);
        container.setHome("c:/apps/apache-tomcat-6.0.18");
        container.start();

        WebDriver driver = new HtmlUnitDriver();
        driver.get("http://localhost:8080/springsprout/member/list.do");

        // logging
        System.out.println(driver.getTitle());
       assertEquals(2, memberRepository.getMemberList().size());
        System.out.println(driver.getPageSource());

        WebElement element = driver.findElement(By.linkText("keesun@whiteship.me"));
        assertNotNull(element);
        element.getAttribute("href");
        container.stop();
    }

이런 식으로 말이죠. 하지만 결과는 참담했습니다. 분명히 빨간색 줄은 테스트가 assertion이 됩니다. Cargo로 서버도 잘 동작합니다. 하지만 WebDriver로 접근해 봤을 때 DB에 넣었던 데이터가 화면에 나와야 하는데 나오지 않습니다. sysout으로 HTML을 찍어 봤지만, 정말로 데이터가 없었습니다.

1. WAR로 배포한 애플리케이션과 테스트 코드가 다른 DB를 사용한다.

이런... 그렇치..mvn package로 묶었을 때 그 안에 들어가는 건 src에 있는 설정 파일이지 test가 아니니깐, 지금 test할 때 사용하는 DB랑은 다른 걸 쓸꺼 아냐 ㅠ.ㅠ 이런 바로.. 그럼 일단 src랑 test랑 같은 DB를 사용하게 설정해보자.(원래 이럼 안되는 건데..)

2. 같은 DB를 사용하지만 여전히 동일한 상황.

@_@ 뭐지.. 왜 이럴까? DBUnit이 데이터를 넣고 확인하는 DB와 서버가 참조하는 DB가 완전히 별개 인 것처럼 보이는데, 트랜잭션이 아예 달라서 그런건가. @_@ 어찌해야 되나.. 아 괴룹네요. 괴로워..
top