확장된 단위 테스트 도구
(Cactus & JUnitEE)
자바스터디 조대협 (http://bcho.tistory.com)
좀더 상세화된 단위 테스트의 단계를 나눠 보면 다음과 같이 나눠볼 수 있다.
l Type 1.코드 단위 테스트
코드상의 로직에 대해서만 테스트를 수행한다.앞 장에서 살펴본 테스트 방법이 일반적인 코드 단위 테스트 방법이다.
그러나 EJB,Servlet과 같은 J2EE 컴포넌트에 대해서 로직이 Dependency를 가지는 경우에는 EJB,Servlet 객체를 직접 연동하는 경우 container (WAS)에 배포하고, 기타 환경 설정이 필요하기 때문에, 로직 테스트를 위해서는 container 환경을 구성하기 전에 동일한 인터페이스를 가지면서 실제 로직은 없고 간단한 return값만 주는 가상의 객체 (Mock Object)를
l Type 2. In-Container 테스트
In container테스트는 , 컴포넌트들의 상호 작용과 컴포넌트와 컨테이너간의 상호작용을 테스트 하는 케이스 인데, POJO기반의 객체의 경우 문제가 없지만 J2EE와 같이 Container 위에서 동작하는 컴포넌트인 경우 Container에 배포하고, Container내에서 테스트 케이스를 수행해야 한다.
예를 들어 Servlet 클래스는 WAS환경 위에서만 작동하기 때문에, 테스트 하기 위해서는 JUnit을
l Type 3. Acceptance 테스트
사용자 요구 사항에 대한 기능 테스트 이다. 좀더 쉽게 설명하면 사용자의 Use Case에 대한 시나리오에 따라 테스트를 하는것인데, 예를 들어 사이트에 LogIn해서 Form에 어떤 값을 넣고 submit 버튼을 누르면 화면상에 어떤 데이터가 출력되어야 한다는 등의 기능적 시나리오에 대한 테스트 이다. HTML 기반의 기능 테스트를 지원하는 테스트 프레임웍으로는 HttpUnit과 이를 확장한 JWebUnit등이 있다.
l Type 4.성능 단위 테스트
Type 1~3는 기능에 대한 테스트를 수행하였다면, Type 4는 비기능적인 이슈중에서 성능에 대한 테스트 방법으로, 각 단위 컴포넌트의 수행 시간에 대한 테스트를 수행하는 것이다. 성능 단위 테스트를 지원하는 프레임웍으로는 JPerfUnit등이 있다.
지금까지 확장된 단위 테스트 방법에 대해서 살펴보았다. 지금부터는 이런 확장된 단위테스트를 수행할 수 있는 구체적인 테스트 프레임웍에 대해서 소개하고자 한다.
(1) In-container test - Catcus & JUnitEE
Type 2에서 언급한 in-container 테스트를 위한 프레임웍으로는 Apache의 Catcus와 JUnitEE이 있다.
두 프레임웍 모두 In-container 테스트를 가능하게 해주지만, JUnitEE의 경우 Catcus에 비해서 상대적으로 사용하기가 쉽다.JUnit의 TestCase를 구현해주고 JUnitEE의 서블릿을 설치하면, WAS에 리모트로 테스트를 수행할 수 있으며, 웹브라우져로도 테스트가 가능하다.
Catcus의 경우 사용법은 JUnitEE에 비해서는 비교적 어렵지만, Servlet,JSP,TagLib에 대한 좀더 전문화된 테스트 프레임웍을 제공한다.
EJB의 경우 Remote Interface가 있는 경우에는 In-container가 아니더라도 WAS외부에서 JUnit을 그러나 J2EE 솔루션 패키지(EAI,EP,ESB) 의 경우 J2EE의 호출 환경이 container 내에서만 호출되도록 디자인된 경우도 있기 때문에 필수적으로 In-container 테스트를 진행해야 할경우도 있다. In-container test의 경우 별도의 테스트 프레임웍 도입에 대한 비용(시간과 교육)이 필요한 만큼 필요성에 대해 고민한 다음 도입 여부를 결정하는 것이 필요하다. |
JUnitEE는 단순하게, TestCase를 컨테이너 안에서 수행할 수 있게 해주는데 그 목적을 둔다. 앞에서 작성한 JUnit 테스트 클래스인 HelloWorldTest와 EmpDAOTest를 Tomcat 컨테이너 안에서 수행해보자
WEB-INF/web.xml에 JUnitEETestServlet을 아래와 같이 설정한다.
<servlet> <servlet-name>JUnitEETestServlet</servlet-name> <description>JUnitEE test framework</description> <servlet-class>org.junitee.servlet.JUnitEEServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>JUnitEETestServlet</servlet-name> <url-pattern>/TestServlet/*</url-pattern> </servlet-mapping> |
< 예제. JUnitEE 테스트 서블릿 설치 >
WEB-INF/testCase.txt라는 파일을 아래와 같이 작성한다. 이 파일에는 TestCase 클래명을 정의한다. 이 클래스명으로 JUnitEE가 단위테스트를 실행할 수 있게 된다.
# test Case Def bcho.test.HelloWorldTest bcho.simpledb.test.EmpDAOTest |
<예제. JUnitEE에서 테스트 클래스를 지정하는 방법 >
위의 방법은 testCase.txt를
다음으로 Tomcat을 기동한후 http://ip:port/webapp_contextname/TestServlet을 수행한다.
<그림. JUnitEE의 테스트 선택화면 >
웹 화면상에서 등록한 테스트 케이스를 확인할 수 있으며 선택해서 테스트를 진행하게 되면, 다음과 같이 결과를 웹상으로 확인할 수 있다.
< 그림 JUnitEE의 테스트 결과 >
간단하게 JUnitEE를
또한 예제의 테스트 방법은 웹브라우져를 통해서 테스트를 수행하는 방법이었는데, 이 외에도 ant task를
2) Cactus
( http://jakarta.apache.org/cactus/)
또다른 in-container 단위 테스트 프레임웍인 Cactus 에 대해서 알아보자, Cactus는 Apache 자카르타 프로젝트의 일부로,JUnitEE가 JUnit 클래스를 서버에서 단순호출 해주는데 비해서 Cactus는 J2EE컴포넌트에 대한 좀더 정교한 테스트를 지원한다.
Cactus에서 지원하는 J2EE 컴포넌트로는 Servlet/JSP, Filter, TagLib,EJB 테스트들이 있으며, 이러한 테스트를 위한 클래스들을 지원한다.
예를 들어 Servlet을 테스트하고자 할 때 JUnitEE를 사용하면 단순한 Java 객체로밖에 Servlet을 테스트할 수 밖에 없다. 그러나Servlet을 제대로 테스트하기 위해서는 HttpRequest 내용을 채워넣고, Servlet을 수행한후 Cookie나 Session 값들을 체크하고, HttpResponse의 내용을 체크해야 한다. 이런 일련의 작업들을 개발자가 일일이 Pure Java Code를
Cactus에서는 이러한 HttpRequest,Cookie,HttpReponse,Session,ServletConfig등 Servlet을 테스트하기 위해서 필요한 클래스들을 내장 클래스 형식으로 지원하여 쉽게 Servlet을 테스트할 수 있도록 해준다.
JSP나 ServletFilter들도 마찬가지 원리로 지원해주게 된다.
이해를 돕기 위해서 간단한 서블릿을 테스트하는 환경을 만들어보자.
서블릿은 Http Request를 통해서 id와 passwd를 입력받고, 이 값을 비교해서 맞으면 session에 id값을 저장하고 Login이 성공하였음을 response로 보내는 간단한 서블릿이다.
public class LogInServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { PrintWriter out = res.getWriter(); HttpSession session = req.getSession(); String id = req.getParameter("id"); String passwd = req.getParameter("passwd"); if(checkLogin(id,passwd)){ // 로그인이 성공하였으면 세션에 ID를 저장한다. session.setAttribute("id", id); out.print(id+" Login failed"); } out.print(id+" Login success"); }// doGet : |
< 테스트할 서블릿 >
이 서블릿을 테스트하기 위해서 Cactus로 테스트 케이스를 만든후, id와 passwd를 HttpRequest로 보낸후, session에 id가 저장되는지 확인한후, HttpResponse에 “success” 라는 문자열을 리턴하는지 확인하면, 서블릿이 정상적으로 동작하는 것을 확인할 수 있다.
Cactus로 만드는 테스트 클래스는 org.apache.cactus.ServletTestCase를 상속 받아서 구현되어야 하며, beginXXX와 testXXX,endXXX 메서드를 구현해야 한다.
beginXXX메서드는 test를 수행하기 전에 WebRequest 객체를 받아서 HttpRequest를 만드는 역할을 하고, testXXX에서는 실제로 테스트를 수행하며, endXXX에서는 Servlet의 수행이 끝난후에 WebResponse 객체를 받아서 HttpResponse 내용으로 테스트 결과를 검증하는 과정을 거친다.
아래 샘플코드를 살펴보자
package bcho.servlet.test.catcus; import org.apache.cactus.ServletTestCase; import org.apache.cactus.WebRequest; import org.apache.cactus.WebResponse; public void beginLogin(WebRequest theRequest) { theRequest.addParameter("id", "bcho"); theRequest.addParameter("passwd","passwd"); { LogInServlet servlet = new LogInServlet(); try{ servlet.init(config); // Call method to test servlet.doGet(request, response); }catch(Exception e){ e.printStackTrace(); assertTrue(false); // Exception이 발생하였을 경우 실패로 간주한다. return; } // Perform some server side asserts assertEquals("bcho", session.getAttribute("id")); } public void endLogin(WebResponse theResponse) { // Asserts the returned HTTP response // 리턴되는 HTML에 로그인 성공 메세지가 있는지 확인한다. String responseTxt = theResponse.getText(); assertTrue(responseTxt.indexOf("success") > 0); } } |
< Cactus 서블릿 테스트 케이스 >
서블릿과 서블릿 테스트 클래스가 완성되었으면 이 환경을 서버에 배포해서 테스트를 수행해보자.
먼저 WEB-INF/lib 디렉토리에 http://jakarta.apache.org/cactus 에서 다운 받은 cactus 관련 라이브러리를 복사한다.
( cactus.jar , common-httpclient.jar,common-logging.jar, junit.jar , aspectjrt.jar ) 다음으로 위에서 만든 서블릿 클래스들을 WEB-INF/classes의 패키지 디렉토리에 알맞게 복사한다.
다음으로 cactus 서블릿들인 ServletRedirector와 ServletTestRunner 클래스를 WEB-INF/web.xml에 다음과 같이 설정한다.
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name> Einstein Unit Tester Web Application </display-name> <servlet-name>ServletRedirector</servlet-name> <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class> <init-param> <param-name>param1</param-name> <param-value>value1 used for testing</param-value> </init-param> </servlet>
<servlet> <servlet-name>ServletTestRunner</servlet-name> <servlet-class>org.apache.cactus.server.runner.ServletTestRunner</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletRedirector</servlet-name> <url-pattern>/ServletRedirector</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletTestRunner</servlet-name> <url-pattern>/ServletTestRunner</url-pattern> </servlet-mapping> </web-app> |
http://{ip}:{port}/{context-root}/ServletTestRunner?suite={서블릿테스트클래스명} |
필자는 localhost의 8080포트에서 context-root를 Catcus로 배포하였고, 테스트 서블릿은 위에서 작성한데로, bcho.servlet.test.catcus.TestLogInServlet으로 구성하였기 때문에, 테스트 URL은 아래와 같이 구성 된다.
XML 형식이 아니라 좀더 정재되고 보기 편한 형태로 테스트 리포트를 보고 싶을 경우에는 Apache Jakarta 프로젝트에서 제공하는 XSL을 적용하면 되는데, 이 XSL은 http://jakarta.apache.org/cactus/misc/cactus-report.xsl 에서 다운 받을 수 있다.
다운 받은 cactus-report.xsl을 WebApplication의 context-root 디렉토리에 저장한후에 QueryString으로 xsl=cactus-report.xsl로 xsl 파일을 다음과 같이 지정할 수 있다.
지금까지 Catcus를
테스트를 브라우져에서 진행하였는데, 단위테스트는 위에서도 설명하였듯이 빌드 작업의 일부로 진행이 되고, 빌드는 통상적으로 자동화가 되기 때문에 Catcus를
지금부터는 대표적인 자바 빌드 도구인 ANT를 통해서 Catcus 테스트를 자동화하는 방법에 대해서 알아보도록 하자.
*.java 파일은 src 디렉토리에 위치하였으며, 개발에 필요한 각종 jar 파일은 lib 디렉토리에 저장하였다. web.xml과 같은 config 파일은 conf 파일에 저장하였다.
<project name="CactusTest" default="cactus-test" > ============================================================================================ 기본 환경 정보 설정 ============================================================================================= --> <!-- 기본 디렉토리 정보 --> <property name="project.name" value="Cactus"/> <property name="lib" value="./lib"/> <property name="src" value="./src"/> <property name="build" value="./build"/> <property name="packaging" value="./pack"/> <property name="conf" value="./conf"/> <property name="cactus.home" value="D:\dev\lib\jakarta-cactus-13-1.7.2"/> <property name="cactus.testlog" value="c:\temp\test-report"/> <property name="tomcat.home" value="D:\dev\apache-tomcat-5.5.23\"/> <!-- jar 파일 경로 --> <property name="cactus.jar" value="${cactus.home}/lib/cactus-1.7.2.jar"/> <property name="cactus-ant.jar" value="${cactus.home}/lib/cactus-ant-1.7.2.jar"/> <property name="commons-httpclient.jar" value="${cactus.home}/lib/commons-httpclient-2.0.2.jar"/> <property name="commons-logging.jar" value="${cactus.home}/lib/commons-logging-1.0.4.jar"/> <property name="aspectjrt.jar" value="${cactus.home}/lib/aspectjrt-1.2.1.jar"/> <property name="cargo.jar" value="${cactus.home}/lib/cargo-0.5.jar"/> <property name="servletapi.jar" value="${lib}/servletapi-2.3.jar"/> <property name="junit.jar" value="${lib}/junit-3.8.1.jar"/> <!-- class path 설정 --> <path id="cactus.classpath"> <pathelement location="${cactus.jar}"/> <pathelement location="${cactus-ant.jar}"/> <pathelement location="${commons-httpclient.jar}"/> <pathelement location="${commons-logging.jar}"/> <pathelement location="${aspectjrt.jar}"/> <pathelement location="${cargo.jar}"/> <pathelement location="${junit.jar}"/> </path> <!-- ============================================================================================= Task 정의 ============================================================================================= --> <!-- Cactus task 정의 --> <taskdef resource="cactus.tasks" classpathref="cactus.classpath"/> <!-- Clean --> <target name="clean"> <delete dir="${build}"/> <mkdir dir="${build}" /> <mkdir dir="${packaging}" /> </target> <!-- ============================================================================================= 컴파일 ============================================================================================= --> <!-- 소스 컴파일 --> <target name="compile" > <javac srcdir="${src}" destdir="${build}"> <classpath> <path refid="cactus.classpath"/> <pathelement location="${servletapi.jar}"/> </classpath> </javac> </target> <!-- ============================================================================================= 패키징 ${build}에 생성된 클래스들과, ${conf}에 있는 web.xml 설정 정보를 모아서 하나의 WAR파일로 패키징 한다. ============================================================================================= --> <!-- WAR 패키징 --> <target name="packaging" depends="compile" > <war warfile="${packaging}/${project.name}.war" webxml="${conf}/web.xml" > <classes dir="${build}" /> <lib dir="${lib}"/> </war> </target> <!-- ============================================================================================= 배포 LOCAL Machine으로 배포하는 스크립트로 tomcat의 /webapps 디렉토리에 war파일을 복사한다. ============================================================================================= --> <!-- 배포 --> <target name="deploy" depends="packaging" > <copy file="${packaging}/${project.name}.war" todir="${tomcat.home}/webapps"/> </target> <!-- ============================================================================================= 테스트 ============================================================================================= --> <!-- Cactus 테스트 --> <target name="cactus-test"> <!-- Run the tests --> <cactus warfile="${packaging}/${project.name}.war" fork="yes" printsummary="yes" failureproperty="tests.failed"> <classpath> <path refid="cactus.classpath"/> <pathelement location="${servletapi.jar}"/> <pathelement location="${build}"/> </classpath> <containerset> <!-- <tomcat5x dir="D:\dev\apache-tomcat-5.5.23" port="8080" /> --> <generic name="local tomcat" port="8080"> <startup target="dummy"/> <shutdown target="dummy"/> </generic> </containerset> <formatter type="brief" usefile="false"/> <formatter type="xml"/> <batchtest todir="${cactus.testlog}"> <fileset dir="./src"> <include name="**/Test*.java"/> </fileset> </batchtest> </cactus> <!-- JUnit(cactus) 테스트 리포트 생성 --> <junitreport todir="${cactus.testlog}"> <fileset dir="${cactus.testlog}" includes="TEST*.xml"/> <report todir="${cactus.testlog}" format="frames"/> </junitreport> <fail if="tests.failed"> cactus Test failed</fail> </target> <target name="dummy"> </target> </project> |
<Cactus 실행용 ANT 스크립트 >
먼저 ant에 cactus 테스크를 추가하자.
CLASSPATH에 cactus에 필요한 클래스들을 추가한후 taskdef를
<!-- class path 설정 --> <path id="cactus.classpath"> <pathelement location="${cactus.jar}"/> <pathelement location="${cactus-ant.jar}"/> <pathelement location="${commons-httpclient.jar}"/> <pathelement location="${commons-logging.jar}"/> <pathelement location="${aspectjrt.jar}"/> <pathelement location="${cargo.jar}"/> <pathelement location="${junit.jar}"/> </path> <taskdef resource="cactus.tasks" classpathref="cactus.classpath"/> |
이제는 cactus 관련 테스크들을 사용할 수 있다.
실제로 cactus 테스크를 사용해야 하는데, 테스크의 내용을 나눠보면 크게, 테스트할 WAR파일 설정, CLASSPATH 설정,테스트용 서버 정보 설정, 테스트할 유닛들 지정 및 로그 정리 절차로 나눠볼 수 있다.
<cactus warfile="${packaging}/${project.name}.war" fork="yes" printsummary="yes" failureproperty="tests.failed"> |
warfile 속성에 테스트할 (서버에 배포한) war 파일을 지정하고, failureproperty에 실패값을 지정한다. 그리고 해당 target 마지막 부분에 이 failureproperty 값으로 아래와 같이 실패 처리를 한다..
<fail if="tests.failed"> cactus Test failed</fail> |
만약 이 처리를 하지 않으면 cactus 단위 테스트가 실제로 실패하더라도 ant의 target에 대한 결과는 Successful로 처리된다.
<classpath> <path refid="cactus.classpath"/> <pathelement location="${servletapi.jar}"/> <pathelement location="${build}"/> </classpath> |
cactus는 in-container 테스트이기 때문에 테스트할 컨테이너를 꼭 지정해야 하는데,catcus에서는 테스트할 서버를 직접 기동해서 WAR파일을 배포하는 것 까지 자동화 할 수 있다.Tomcat과 Orion,Resin,WebLogic까지 지원하는데 자세한 내용은 cactus의 문서를 참고하도록 하고 필자는 generic이란 element를
<containerset> <generic name="local tomcat" port="8080"> <startup target="dummy"/> <shutdown target="dummy"/> </generic> </containerset> : <target name="dummy"> </target> |
그리고 두개의 element 를 지정해야 하는데, startup과shutdown 엘리먼트이다.
이 엘리먼트는 서버가 기동되어 있지 않은 경우 실행되어 서버를 기동시키고 테스트가 끝난후에 서버를 자동으로 내리는 역할을 하는 “target”을 지정해야 한다. (필수 element 이다.)
필자는 서버의 기동과 정지를 ANT 내에서 처리 하지 않고 직접 처리하였기 때문에, “dummy”라는 아무 작업도 하지 않는 target을 정의하여 지정하였다.
다음으로 Unit 테스트에 대한 로그를 저장하는 설정을 진행한다. <formatter> 라는 element를 사용하는데 Cactus는 JUnit의 확장이기 때문에 JUnit의 formatter element를 지원한다.
여기서 사용한 formatter의 type 속성은 두가지로 brief와 xml 을 사용하였다.
Brief는 테스트의 실패이유를 대략적으로 출력해주는 속성이고 xml은 테스테에 대한 각종 정보(통계,프로퍼티등등)을 상세하게 저장해주는 속성이다.
brief 값은 usefile=”false” 로 설정하여, stdout(화면)으로 출력하도록 하였고, xml을 usefile을 설정하지 않아서 파일로 저장되도록 하였다. 파일 경로는 다음 설명할 <batchtest> element에서 지정되는 todir 디렉토리에 저장되게 된다.
<formatter type="brief" usefile="false"/> <formatter type="xml"/> |
이제 실제로 테스트할 테스트 클래스들을 선택해야 하는데 한꺼번에 지정하기 위해서 fileset element를
<batchtest todir="${cactus.testlog}"> <fileset dir="./src"> <include name="**/Test*.java"/> </fileset> </batchtest> |
cactus 테스트를 수행하기 위한 준비가 완료되었고 여기까지 설정하면 실제로 테스트를 수행할 수 있다.
여기에 하나 덧붙여서 설명하면, 테스트 결과는 formatter에서 type=”xml”로 설정한 부분이 XML파일로 저장된다. 그러나 이 파일을 읽기가 쉽지 않기 때문에, 이 xml 파일을
<junitreport todir="${cactus.testlog}"> <fileset dir="${cactus.testlog}" includes="TEST*.xml"/> <report todir="${cactus.testlog}" format="frames"/> </junitreport> |
아래는 <junitreport> 를
<그림 junitreport를
자아, 이제 cactus를
다소 복잡하기는 하지만 JUnitEE보다 훨씬 더 정교한 J2EE 컴포넌트 테스트를 지원하고, ANT 스크립트 역시 개발 빌드 과정에서 필요한 부분에 약간만 더 해진것이기 때문에 정교한J2EE 컴포넌트 테스트를 진행하기 위해서는 Cactus를 사용하는 것을 권장한다.
'ALM > Test Automation' 카테고리의 다른 글
Junit best practices (0) | 2008.03.12 |
---|---|
단위테스트 3회 - 커버러지 분석과 단위 성능 테스트 (3) | 2008.03.12 |
Test Coverage Rate (0) | 2008.02.11 |
Cobertura에서 코드 커버러지 분석결과가 잘못 나올때.. (0) | 2008.01.17 |
Cactus에서 JUnit 테스트 케이스 재활용 하기 (2) | 2008.01.17 |