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

출처 블로그 > julymorning4님의 블로그
원본 http://blog.naver.com/julymorning4/100018922422
스트럿츠에서의 파일 업로드

작성자 : 김길재


스트럿츠의 폼에서 File을 인식하기 못하기 때문에

org.apache.struts.upload.FormFile 클래스를 사용하여야 합니다.

제가 작업하던중 스트럿츠의 파일 업로드에 사용한 소스입니다.

궁금하신 점은 답글 남겨주시거나 메일 보내주시면 대답해 드리겠습니다. ^^


///////////////// image_insert.jsp/////////////////////////////////////


<%@ page contentType="text/html;charset=EUC-KR" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>


<html:form action="/ImageInsert" enctype="multipart/form-data" >
  <table>
    <TR bgcolor=FFFFFF>
                      <TD height="30" bgcolor='#E6EBF0'>* 이미지(대)</TD>
                      <TD colspan="2">
                        <html:file property="image_path[0]" size="30"/>        
                      </TD>
                    </TR>
                    <TR bgcolor=FFFFFF>
                      <TD height="30" bgcolor='#E6EBF0'>* 이미지(중)</TD>
                      <TD colspan="2">
                        <html:file property="image_path[1]" size="30"/>                
                      </TD>
                    </TR>
                    <TR bgcolor=FFFFFF>
                      <TD height="30" bgcolor='#E6EBF0'>* 이미지(소)</TD>
                      <TD colspan="2">
                        <html:file property="image_path[2]" size="30"/>                
                      </TD>
                    </TR>
  </table>
</form:html>

///////////////////struts-config.xml///////////////////////////////////

<form-beans>
  <form-bean name="imageinsertForm" type="ImageInsertForm"/>
</form-beans>

<action-mappings>
  <action          
                path="/ImageInsert"
                type="ImageInsertAction"
            name="imageinsertForm"                        
            validate="false"
            input="/image_insert.jsp"
        />

///////////////////ImageInsertForm.java///////////////////////////////

import org.apache.struts.upload.FormFile;
public class ImageInsertForm  extends ActionForm
{

    private FormFile[] image_path = new FormFile[3];
   
    public FormFile[] getImage_path() {
                return image_path;
        }
        /**
         * @param image_path The image_path to set.
         */
        public void setImage_path(FormFile[] image_path) {
                this.image_path = image_path;
        }
}        

////////////////////ImageInsertAction.java/////////////////////////////

import org.apache.struts.upload.FormFile;

public class GoodsInsertAction extends BaseAction
{
        public ActionForward execute(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response)
        {
                   FormFile image_path[] = ((ImageInsertForm)form).getImage_path();
                   ImageDAO imageDAO = new ImageDAO();
                   imageDAO.imageInsert( image_path );
                  
                   return (mapping.findForward( "image_insert_success" ));
        }
}

/////////////////////////// GoodsFile.java////////////////////////////
/**
* @author Administrator
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class GoodsFile
{
        private String fileName = null;
        private String tempFileName = null;

        public String getFileName() {
                return fileName;
        }
       
       
        public String getTempFileName() {
                return tempFileName;
        }
       


        public void setFileName(String fileName) {
                this.fileName = fileName;
        }



        public void setTempFileName(String tempFileName) {
                this.tempFileName = tempFileName;
        }
}

////////////////////ImageDAO.java///////////////////////////////////

import org.apache.struts.upload.FormFile;

public class ImageDAO
{

    public  void imageInsert( FormFile[] image_path )
    {
        Connection con=null;
        PreparedStatement pstmt=null;
        ResultSet rs = null;
        String sql = "";
        GoodsFile goodsFile[] = new GoodsFile[ 3 ];
        String path = "D:\\upload";

        try
        {
                 for( int i = 0 ; i < 3 ; i++ )
                {
                        goodsFile[ i ] = FileUploadUtil.doFileUpload( image_path[ i ] , gcode , Integer.toString( ( i + 1 ) ) );
                }

                con = DriverManager.getConnection(ConnectionPoolManager.URL_PREFIX+"oraclejava");
               
                sql = "INSERT INTO IMAGES( IMAGE_PATH1 , IMAGE_PATH2 , IMAGE_PATH3 ) VALUES(  :IMAGE_PATH1 , :IMAGE_PATH2 , :IMAGE_PATH3 );
                pstmt = con.prepareStatement(sql);
                pstmt.setString( 1 , goodsFile[ 1 ].getFileName()  );
                    pstmt.setString( 2 , goodsFile[ 2 ].getFileName() );
                    pstmt.setString( 3 , goodsFile[ 3 ].getFileName() );
                pstmt.executeUpdate();
 
                }
        catch( Exception e )
        {
                return null;
        }
        finally
        {
                     try
                {

                    if ( rs != null )
                        rs.close();
                    if ( pstmt != null )
                        pstmt.close();
                    if ( con != null )
                        con.close();
                }
                catch ( Exception ignore )
                {
               
        }         
    }
}


///////////////////////FileUploadUtil.java/////////////////////////////
ublic class FileUploadUtil
{
        public static GoodsFile doFileUpload(FormFile fileList  )
        throws FileNotFoundException, IOException
        {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream stream = fileList.getInputStream();

                               

                //파일을 업로드할 절대 경로를 지정해야 한다.
                String path = "D:\\upload\\";
                OutputStream bos = new FileOutputStream( path + fileList.getFileName()  );
                int bytesRead = 0;
                byte[] buffer = new byte[8192];
                while ((bytesRead = stream.read(buffer, 0, 8192)) != -1)
                {
                        bos.write(buffer, 0, bytesRead);
                }
                bos.close();
                stream.close();

        GoodsFile boardFile = new GoodsFile();
        boardFile.setFileName(fileList.getFileName());
        boardFile.setTempFileName(fileList.getFileName() );

        return boardFile;
}
}

06 XML 마지막날.

프로그래밍/ETC 2007. 11. 28. 09:44 Posted by galad

 XML -> 정보 전달 수단, 문서 작성, HTML을 보완(대체가 아니다)


① Well-Formed XML

② Valid XML

  Well-Formed XML + DTD(or Schema)


DTD의 단점

- XML 문법과 다르다.

- 표현할 수 있는 데이터 종류가 적다. 풍부한 표현이 불가.


-> 최근에는 스키마가 많이 쓰이지만, 아직도 혼용되고 있다.


Schema

- 풍부한 표현이 가능. 사용자 정의 타입도 만들 수 있다.


=> DOM, SAX 등의 api를 이용해서 JAVA에서 XML을 다룰 수 있어야 한다.

JSP에서 배움.

'프로그래밍 > ETC' 카테고리의 다른 글

[XPath] String으로 InputSource 생성하기  (0) 2009.03.02
[에러] javax.xml.xpath.XPathFactory 사용 시, newInstance() 생성이 안될 때  (0) 2009.03.02
05 스키마...  (0) 2007.11.28
04 DTD...  (0) 2007.11.28
03 엘리먼트 내용  (0) 2007.11.28

05 스키마...

프로그래밍/ETC 2007. 11. 28. 09:44 Posted by galad

<<< memberlist2.xsd >>>


<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <!-- 주석 -->
    <xsd:annotation>
        <xsd:appinfo source="memberlist2.xml">
            memberlist2.xml
        </xsd:appinfo>
        <xsd:documentation source="memberlist2.xml" xml:lang="ko">
            dtd로 만든 memberlist를 xml schema로 바꾼다.
        </xsd:documentation>
    </xsd:annotation>
   
    <!-- 루트 엘리먼트 선언 -->
    <xsd:element name="MemberList">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="Member" type="ctMember" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
   
    <!-- 글로벌 컴플렉스 타입 정의 -->
    <xsd:complexType name="ctMember">
        <xsd:sequence>
            <xsd:element name="name" type="xsd:string"/>
            <xsd:element name="age" type="xsd:int"/>
            <xsd:element name="sex" type="ctSex"/>           
            <xsd:element name="job" type="ctJob"/>
            <xsd:element name="address" type="xsd:string"/>
            <xsd:element name="tel" type="xsd:string"/>
        </xsd:sequence>
       
        <!-- 속성 선언 -->
        <xsd:attribute name="kind" type="stMemberKind" use="required"/>
        <xsd:attribute name="id" type="xsd:ID" use="required"/>
    </xsd:complexType>
   
    <xsd:complexType name="ctJob">
        <xsd:sequence>
            <xsd:element name="company_name" type="xsd:string"/>
            <xsd:element name="company_tel" type="ctCompanyTel"/>
            <xsd:element name="company_address" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
   
    <xsd:complexType name="ctSex">
        <xsd:simpleContent>
            <xsd:extension base="xsd:string">
                <xsd:attribute name="s" type="stSex" use="required"/>
            </xsd:extension>
        </xsd:simpleContent>
    </xsd:complexType>
   
    <xsd:complexType name="ctCompanyTel">
        <xsd:simpleContent>
            <xsd:extension base="xsd:string">
                <xsd:attribute name="com_tel" type="stCompanyTel" use="required"/>
            </xsd:extension>           
        </xsd:simpleContent>
    </xsd:complexType>
   
    <!-- 사용자 정의 심플 타입 정의 -->
    <xsd:simpleType name="stMemberKind">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="유료"/>
            <xsd:enumeration value="무료"/>
        </xsd:restriction>
    </xsd:simpleType>
   
    <xsd:simpleType name="stSex">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="man"/>
            <xsd:enumeration value="woman"/>
        </xsd:restriction>
    </xsd:simpleType>
   
    <xsd:simpleType name="stCompanyTel">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="fax"/>
            <xsd:enumeration value="H.P."/>
            <xsd:enumeration value="tel"/>
        </xsd:restriction>
    </xsd:simpleType>
</xsd:schema>



<<< memberlist2.xml >>>


<?xml version="1.0" encoding="UTF-8"?>
<MemberList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="memberlist2.xsd">
    <Member kind="무료" id="askjdfkd">
        <name>김종식</name>
        <age>28</age>
        <sex s="woman">여자</sex>
        <job>
            <company_name>itea</company_name>
            <company_tel com_tel="H.P.">001</company_tel>
            <company_address>서소문</company_address>
        </job>
        <address>안산?</address>
        <tel>000</tel>
    </Member>   
</MemberList>

'프로그래밍 > ETC' 카테고리의 다른 글

[에러] javax.xml.xpath.XPathFactory 사용 시, newInstance() 생성이 안될 때  (0) 2009.03.02
06 XML 마지막날.  (0) 2007.11.28
04 DTD...  (0) 2007.11.28
03 엘리먼트 내용  (0) 2007.11.28
02 xml 복습 및 2일째  (0) 2007.11.28

04 DTD...

프로그래밍/ETC 2007. 11. 28. 09:43 Posted by galad

예제


 

<<< memberlist.dtd >>>


<?xml version="1.0" encoding="UTF-8"?>

<!-- 엘리먼트 선언 -->
<!ELEMENT MemberList (Member*)>
    <!ELEMENT Member (name, age, sex, job, address, tel)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        <!ELEMENT sex (#PCDATA)>
        <!ELEMENT job (company_name, company_tel+, company_address)>
            <!ELEMENT company_name (#PCDATA)>
            <!ELEMENT company_tel (#PCDATA)>
            <!ELEMENT company_address (#PCDATA)>
        <!ELEMENT address (#PCDATA)>
        <!ELEMENT tel (#PCDATA)>
       
<!-- 속성 선언 -->
<!ATTLIST Member
    kind (유료|무료) #REQUIRED
    id ID #REQUIRED>
<!ATTLIST sex
    s (man|woman) #REQUIRED>
<!ATTLIST company_tel
    ctel (fax|HP|tel) #REQUIRED>




<<< memberlist.xml >>>


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE MemberList SYSTEM "memberlist.dtd">

<MemberList>
    <Member kind="무료" id="b1">
        <name>성홍제</name>
        <age>27</age>
        <sex s="man">남자</sex>
        <job>
            <company_name>ITEA</company_name>
            <company_tel ctel="tel">000</company_tel>
            <company_address>서소문</company_address>
        </job>
        <address>염리동</address>
        <tel>000</tel>
    </Member>
    <Member kind="유료" id="b2">
        <name>정은진</name>
        <age>25</age>
        <sex s="woman">여자</sex>
        <job>
            <company_name>ITEA</company_name>
            <company_tel ctel="HP">011</company_tel>
            <company_address>서소문</company_address>
        </job>
        <address>화성시</address>
        <tel>001</tel>
    </Member>
    <Member kind="무료" id="b3">
        <name>신석만</name>
        <age>29</age>
        <sex s="man">남자</sex>
        <job>
            <company_name>ITEA</company_name>
            <company_tel ctel="fax">013</company_tel>
            <company_address>서소문</company_address>
        </job>
        <address>서울시</address>
        <tel>012</tel>
    </Member>
    <Member kind="유료" id="b4">
        <name>손영범</name>
        <age>29</age>
        <sex s="man">남자</sex>
        <job>
            <company_name>ITEA</company_name>
            <company_tel ctel="HP">012</company_tel>
            <company_address>서소문</company_address>
        </job>
        <address>대구시</address>
        <tel>002</tel>
    </Member>
</MemberList>

'프로그래밍 > ETC' 카테고리의 다른 글

06 XML 마지막날.  (0) 2007.11.28
05 스키마...  (0) 2007.11.28
03 엘리먼트 내용  (0) 2007.11.28
02 xml 복습 및 2일째  (0) 2007.11.28
01....  (0) 2007.11.28

03 엘리먼트 내용

프로그래밍/ETC 2007. 11. 28. 09:42 Posted by galad
 XML

2006/08/16 10:14

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

☆ 자식 엘리먼트

엘리먼트의 내용으로 자식 엘리먼트를 포함할 수 있다.




 
☆ 엔티티 참조
자주 쓰이는 내용을 엔티티로 정의하고, XML문서에서 엔티티로 정의된 내용과 동일한 내용이 작성되어야 할 부분에 엔티티 참조를 사용한다.
 
☆ 문자 참조
문자 집합 코드표상에 언급되어 있는 코드값을 직접 사용하여 문자를 나타내는 것이다.
 

 
 
☆ CDATA 섹션
대부분의 문자 데이터인 PCDATA(Parsed Character DATA)는 XML 파서가 해석하는 데이터를 말한다.
CDATA 섹션 내에 정의된 문자 데이터는 XML 프로세서가 해석하지 않고 바로 응용프로그램에게 전달한다.
<![CDATA[문자 데이터]]>
 
 

 

☆ 프로세싱 지시자(Processing Instruction)

☆ 공백 문자열

하나 이상의 공백 문자들로 구성된 문자열

XML 1.0권고안에서는 스페이스(#x20), 탭(#x9), 캐리지 리턴(#xd), 라인피드(#xa)만을 공백 문자로 분류하고 있다.


★ 속성



 
 
★ 주석
<!-- 주석 내용 -->
 
★ 프로세싱 지시자
 

'프로그래밍 > ETC' 카테고리의 다른 글

06 XML 마지막날.  (0) 2007.11.28
05 스키마...  (0) 2007.11.28
04 DTD...  (0) 2007.11.28
02 xml 복습 및 2일째  (0) 2007.11.28
01....  (0) 2007.11.28

02 xml 복습 및 2일째

프로그래밍/ETC 2007. 11. 28. 09:41 Posted by galad

 

 

 
 
★ XML 선언문법
반드시 XML 문서 첫 줄에 기술되어야 한다.
<?xml version="버젼번호" encoding="인코딩방식" standalone="yes|no"?>
 
<? 와 xml 문자 사이에 공백을 두어서는 안 된다.
무조건 맨 첫 줄에 기술되어야 한다. 주석도 불가!
버전 속성은 반드시 있어야 함.
인코딩과 스탠드 얼론은 생략 시, 디폴트 - UTF-8, no 값으로 처리.
스탠드얼론 - parser가 xml문서를 해석할 때 외부 DTD문서 참조 여부
 
★ 엘리먼트
- 모든 xml문서는 단 하나의 루트 엘리먼트를 갖는다
- 엘리먼트는 시작 태그와 끝 태그로 구성되면 태그명은 동일해야 한다.
- 부가적인 정보를 나타내는 속성을 가질 수 있다.
- 시작태그와 끝 내그 사이에는 엘리먼트의 실질적인 내용이 오는데, 문자 데이터 및
 자식 엘리먼트가 올 수 있다.
 

 
 
★ 엘리먼트의 종류
① 내용을 가지는 엘리먼트
- 문자 데이터나 자식 엘리먼트를 내용으로 갖는 엘리먼트
<book>
   <title>자연과 인간</title>
</book>
 
② 내용이 없는 빈 엘리먼트
- 문자 데이터나 자식 엘리먼트를 갖지 않는 엘리먼트
<image src="d:\temp\image1.gif"/>
or
<image src="d:\temp\image1.gif"></image>
 
 

 
 
 

 

 
 
 

 
 
★ 엘리먼트 내용
- 문자 데이터 : XML 프로세서가 해석할 수 있는 내용 중에서 마크업을 제외한 부분
 

 
 
- 문자 데이터 내에는 '&'문자와 '<'문자를 사용할 수 없다. '&'문자는 엔티티 참조의 시작을 의미하며, '<'문자는 엘리먼트의 태그, CDATA 섹션의 시작을 의미하기 때문이다.
- 빌트인 엔티티의 참조 또는 문자 참조로 사용이 가능하다.
 
 

 
 
 

'프로그래밍 > ETC' 카테고리의 다른 글

06 XML 마지막날.  (0) 2007.11.28
05 스키마...  (0) 2007.11.28
04 DTD...  (0) 2007.11.28
03 엘리먼트 내용  (0) 2007.11.28
01....  (0) 2007.11.28

01....

프로그래밍/ETC 2007. 11. 28. 09:40 Posted by galad

<?xml version="1.0" encoding="utf-8"?>


<책목록>
 <책>
  <제목>제길 춥네</제목>
  <저자>lonelycat</저자>
 </책>
</책목록>


저장 시 UTF-8 로 선택해서 저장해야만 익스플로러에서 제대로 보인다.


인코딩이 euc-kr 이면 아스키로..

'프로그래밍 > ETC' 카테고리의 다른 글

06 XML 마지막날.  (0) 2007.11.28
05 스키마...  (0) 2007.11.28
04 DTD...  (0) 2007.11.28
03 엘리먼트 내용  (0) 2007.11.28
02 xml 복습 및 2일째  (0) 2007.11.28
Log4J 적용 사례(따라하기) - 손정호
아래 글은 SKT 소액 결재 개발팀 손정호 님이 작성하신 글임을 알려 드립니다.

참고로 preparedStatement에서 적용한 예 입니다.

====================================================================

Log4j Summary

이번 WebChannel 개발시에 적용된 Log4j 환경을 바탕으로 작성한 간단한 summary입니다.

1. 다운로드

다운로드http://logging.apache.org/log4j/docs/download.html
매뉴얼http://logging.apache.org/log4j/docs/documentation.html
API spechttp://logging.apache.org/log4j/docs/api/index.html

2. 구조
Log4j는 크게 3가지 요소로 구성되어 있습니다.
① Logger : logging 메시지를 Appender에 전달합니다.
② Appender : 전달받은 logging 메시지를 원하는 곳으로 보내는 매개체의 역할을 합니다.
   아래 표는 Appender의 종류입니다. API에서 보고 이해가 된 선에서 적었습니다.
ConsoleAppender        로그 메시지를 콘솔에 출력합니다.
DailyRollingFileAppender        로그 메시지를 파일로 저장합니다.
DatePattern 옵션에 따라 원하는 기간마다 로그파일을 갱신합니다.
ExternallyRolledFileAppender        
FileAppender        직접적으로 사용되지 않고 DailyRollingFileAppender와 RollingFileAppender의 superclass로 사용되는듯 합니다.
JDBCAppender        로그 메시지를 DB에 저장합니다. 현재는 완벽하지 않으니 왠만하면 차기 버전에서 사용하라고 하는 것 같습니다.
JMSAppender        로그 메시지를 JMS Topic으로 보냅니다.
NTEventLogAppender        NT 이벤트 로그를 위한 Appender. 윈도우에서만 사용가능합니다.
NullAppender        내부적으로만 사용되는 Appender입니다.
RollingFileAppender        로그 메시지를 파일로 저장합니다. 설정된 size를 초과하면 로그파일이 갱신됩니다.
SMTPAppender        로그 메시지를 지정된 이메일로 발송합니다.
SocketAppender        로그 메시지를 socket을 이용해서 지정된 곳으로 보냅니다.
SocketHubAppender        위와 비슷하게 사용하는듯 합니다.
SyslogAppender        로그 메시지를 원격 syslog deamon으로 보냅니다.
TelnetAppender        로그 메시지를 telnet을 통해 보낸다는 것 같습니다. 원격 모니터링, 특히 servlet의 모니터링에 유용하다고 합니다.
WriterAppender        FileAppender처럼 주로 superclass로서 사용되는듯 합니다.
③ Layout : logging 메시지의 출력 형식을 지정합니다.
        - 아래에서 설명.

3. 로깅레벨
FATAL : 가장 크리티컬한 에러가 발생했을 때 사용합니다.
ERROR : 일반적인 에러가 발생했을 때 사용합니다.
WARN : 에러는 아니지만 주의가 필요할 때 사용합니다.
INFO : 일반적인 정보가 필요할 때 사용합니다.
DEBUG : 일반적인 정보를 상세히 나타낼 때 사용합니다.

로깅레벨의 우선순위는 FATAL이 가장 높고 DEBUG가 가장 낮습니다.
예를 들어 레벨을 WARN으로 설정하면 WARN이상되는 로그(FATAL, ERROR, WARN)만
출력합니다.

4. 환경설정
- Log4j의 환경설정은 직접 코드에서 메서드를 이용하는 방법과 properties 파일을 이용하는 방법, XML파일을 이용하는 방법이 있습니다.
① 코드에서 설정
String layout = "%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n";
String logfilename = "DailyLog.log";
String datePattern = ".yyyy-MM-dd ";

PatternLayout patternlayout = new PatternLayout(layout);
DailyRollingFileAppender appender = new DailyRollingFileAppender(patternlayout, logfilename, datePattern);
logger.addAppender(appender);
logger.setLevel(Level.INFO);
logger.fatal("fatal!!");

위 코드처럼 설정하시면 됩니다.


② properties 파일로 설정
#---------- file logging ----------
log4j.rootLogger=INFO, rolling
#---------- consol logging -----------
#log4j.rootLogger=INFO, stdout
#---------- file, console logging -----------
#log4j.rootLogger=INFO, stdout, rolling
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
log4j.appender.rolling.File=/WEB_BACKUP1/pgw_log/webchannel.log
log4j.appender.rolling.Append=true
#---------- every day renew ------------
log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
#---------- every month renew ------------
#log4j.appender.rolling.DatePattern='.'yyyy-MM
#---------- every week renew ------------
#log4j.appender.rolling.DatePattern='.'yyyy-MM-ww
#---------- every 12hours renew -------------
#log4j.appender.rolling.DatePattern='.'yyyy-MM-dd-a
#---------- every hour renew --------------
#log4j.appender.rolling.DatePattern='.'yyyy-MM-dd-HH
#---------- every min renew --------------
#log4j.appender.rolling.DatePattern='.'yyyy-MM-dd-HH-mm
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling.layout.ConversionPattern=[%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n


위 properties 파일은 실제 WebChannel에 적용한 파일입니다.
- log4j.rootLogger=INFO, rolling
        : 로깅레벨을 ‘INFO’로 하고 ‘rolling’이라는 이름의 Appender를 사용한다.
        위 properties파일에는 ConsoleAppender(stdout)와 DailyRollingFileAppender(rolling)가
정의되어 있습니다.
- log4j.rootLogger=INFO, stdout : console에만 출력
- log4j.rootLogger=INFO, stdout, rolling : console과 file 로 출력
위처럼 설정이 가능합니다.
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        : ConsoleAppender의 이름은 ‘stdout’으로 한다.
- log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
        : DailyRollingFileAppender의 이름은 ‘rollong’으로 한다.
- log4j.appender.rolling.File=/WEB_BACKUP1/pgw_log/webchannel.log
        : 로그파일의 위치와 파일명을 지정한다.
- log4j.appender.rolling.Append=true
        : 서버 restart시에도 파일이 reset되지 않는다.
- log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
        : DatePattern 을 ‘매일갱신’으로 설정. 매일 자정이 지나면
파일명 뒤에 날짜가 붙는다.
        ex) webchannel.log.2005-11-21
- log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
        : layout을 PatternLayout으로 설정.
- log4j.appender.rolling.layout.ConversionPattern=[%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n
        : 로그의 출력 형식을 설정. 아래 설명.

# log4j.appender.rolling.MaxFileSize=500KB
: 파일의 최대size 설정하는 부분인데 서버 기동시 최초에 이 부분의 property를 읽지 못했다는 경고가 자꾸 떠서 삭제 했습니다. 설정하지 않으면 Default로 10MB가 설정된다고 합니다.

#### properties 파일의 변경사항은 server restart시에 적용됩니다. ####
③ XML 파일로 설정
현재 잘 모르니 넘어가겠습니다.-_-


5. 설정 포맷
① DatePattern 설정 포맷
'.'yyyy-MM         매달 첫번째날에 로그파일을 변경합니다
'.'yyyy-ww         매주의 시작시 로그파일을 변경합니다.
'.'yyyy-MM-dd        매일 자정에 로그파일을 변경합니다.
'.'yyyy-MM-dd-a        자정과 정오에 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH        매 시간의 시작마다 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH-mm        매분마다 로그파일을 변경합니다.




② PatternLayout 설정 포맷
%p        debug, info, warn, error, fatal 등의 로깅레벨이 출력된다.
%m        로그내용(코드상에서 설정한 내용)이 출력됩니다.
ex) logger.info("log"); 라고 코딩했다면 ‘log’가 로그 내용임.
%d        로깅 이벤트가 발생한 시간을 기록합니다.
포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}
같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
%t        로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
%%        % 표시를 출력하기 위해 사용한다.
%n        플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.
%c        카테고리를 표시합니다.
ex) 카테고리가 a.b.c 처럼 되어있다면
%c{2}로 설정하면 b.c 가 출력됩니다.
%C        클래스명을 포시합니다.
ex) 클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면
%C{2}는 xyz.SomeClass 가 출력됩니다
%F        로깅이 발생한 프로그램 파일명을 나타냅니다.
%l        로깅이 발생한 caller의 정보를 나타냅니다
%L        로깅이 발생한 caller의 라인수를 나타냅니다
%M        로깅이 발생한 method 이름을 나타냅니다.
%r        어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
%x        로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를
출력합니다.
%X        로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를
출력합니다.

ex) [%d] %-5p  at %C{3}.%M(%13F:%L) %3x - %m%n
 [2005-11-23 10:43:21,560] INFO   at
pgw.database.PGWBoardDAO.selectList(PGWBoardDAO.java:146) -
========== PGWBoardDAO#selectList ==========

포맷의 각 색깔별로 출력되는 실제 예입니다. 포맷 중간에 원하는 단어(at)나
기호(`.` , `-`)등을 넣으면 그대로 출력됩니다.



6. 실제 적용 예

다운받은 log4j.jar파일을 원하는 디렉토리에 복사하고 weblogic의
startWebLogic.cmd내의 classpath에 잡아줍니다. buildpath도 잡아주셔야 합니다.

① PGWBoardDAO.java
//import 해줍니다.
import org.apache.log4j.Logger;
.
//중략/

public class PGWBoardDAO extends PGWDAO {
    //parameter로 받은 이름의 instance를 생성합니다.
    static Logger logger = Logger.getLogger("PGWBoardDAO");
.
//중략/
.
public PGWBean selectList(HashMap hashMap) throws Exception {
.
/중략/
.
//            pstmt = conn.prepareStatement(sql.toString());
//LoggableStatement instance생성. LoggableStatement는 아래에서 설명.
            pstmt = new LoggableStatement(conn, sql.toString());

.
//중략/
.
pstmt.setInt(nIdx++, ((curPage-1)*listSize) + 9);
          pstmt.setInt(nIdx++, (curPage-1)*listSize);
        //주어진 로그내용을 ‘INFO’레벨로 출력합니다.
getQueryString()으로 ‘?’가 실제데이터로 치환된 query를 출력합니다.
            logger.info("\n======== PGWBoardDAO#selectList ========\n "
                        + ((LoggableStatement)pstmt).getQueryString() +
                       "\n========================================\n");


.
//중략/
.

        } catch (SQLException se) {
            System.out.println("PGWBoardDAO.selectList SQLException ====" + se);
           //Exception은 ‘ERROR’레벨로 출력합니다.
            logger.error("\n==== PGWBoardDAO#selectList Exception ====" , se );
            return null;
        } catch (Exception e) {
            System.out.println("PGWBoardDAO.selectList Exception ====" + e);
            logger.error("\n==== PGWBoardDAO#selectList Exception ====" , e );
            return null;
        } finally {
.
//중략/


- 위 코드에서 INFO 레벨의 로그는 주어진 로그를 출력하고,
ERROR 레벨의 로그는 발생한 Exception을 로그로 출력합니다.

로그의 출력메서드는 2가지 형식을 지원합니다.
logger.fatal(Object message)        logger.fatal(Object message, Throwable t)
logger.error(Object message)        logger.error(Object message, Throwable t)
logger.warn(Object message)          logger.warn(Object message, Throwable t)  
logger.info(Object message)          logger.info(Object message, Throwable t)  
logger.debug(Object message)         logger.debug(Object message, Throwable t)

- Throwble 타입의 변수를 parameter로 받는 메서드를 이용하면 원하는 위치에서
원하는 Exception을 발생시킬 수도 있습니다.

- 위 코드에서 INFO 레벨의 로그는 주어진 내용를 출력하고,
ERROR 레벨의 로그는 발생한 Exception을 로그로 출력합니다.


② LoggableStatement.java
- 이 클래스는 query를 로그로 출력할 때 부가적으로 필요한 클래스로 PreparedStatement의 ‘?’를 실제 데이터로 치환해서 출력하는 기능을 합니다.
이 클래스는 Interface인 PreparedStatement를 구현하는 클래스로 파일이름은 임의로 정하셔도 됩니다.
클래스내에는 PrepareddStatement의 메서드를 오버라이딩한 메서드와 넘어온 데이터를 ArrayList에 넣어주는 메서드, 그리고 query의 ‘?’를 치환해 리턴해주는 메서드를 구현합니다.

//PreparedStatement 와 ArrayList를 import 해줍니다.
//메서드 오버라이딩시에 필요한 클래스도 추가적으로 import 해줍니다.
import java.sql.PreparedStatement;
import java.util.ArrayList;

public class LoggableStatement implements PreparedStatement {

        private ArrayList parameterValues;
 
    private String sqlTemplate;

    private PreparedStatement wrappedStatement;

//connection.prepareStatement(String sql) 대신에 사용할 생성자 입니다.
//PreparedStatement Object를 생성, query를 String에 담고 ArrayList를 생성합니다.
    public LoggableStatement(Connection connection, String sql)
            throws SQLException {
            wrappedStatement = connection.prepareStatement(sql);
            sqlTemplate = sql;
            parameterValues = new ArrayList();
    }

.
//중략/
.



//실제로 필요한 메서드만 오버라이딩 하고, 나머지는 auto generate하시면 됩니다.
//여기서는 query문 실행관련 메서드와 setInt, setString, setDate, setCharacterStream 을 오버라이딩 했습니다.
        public boolean execute() throws java.sql.SQLException {
            return wrappedStatement.execute();
    }

        public boolean execute(String sql) throws java.sql.SQLException {
            return wrappedStatement.execute(sql);
    }

        public int[] executeBatch() throws java.sql.SQLException {
            return wrappedStatement.executeBatch();
    }

        public java.sql.ResultSet executeQuery() throws java.sql.SQLException {
            return wrappedStatement.executeQuery();
    }

        public java.sql.ResultSet executeQuery(String sql)
            throws java.sql.SQLException {
            return wrappedStatement.executeQuery(sql);
    }

        public int executeUpdate() throws java.sql.SQLException {
            return wrappedStatement.executeUpdate();
    }

        public int executeUpdate(String sql) throws java.sql.SQLException {
            return wrappedStatement.executeUpdate(sql);
    }

        public java.sql.Connection getConnection() throws java.sql.SQLException {
            return wrappedStatement.getConnection();
    }

       
public void setCharacterStream(
            int parameterIndex,
            java.io.Reader reader,
            int length)
            throws java.sql.SQLException {
            wrappedStatement.setCharacterStream(parameterIndex, reader, length);
            saveQueryParamValue(parameterIndex, reader);

    }

        public void setDate(int parameterIndex, java.sql.Date x)
            throws java.sql.SQLException {
            wrappedStatement.setDate(parameterIndex, x);
            saveQueryParamValue(parameterIndex, x);
    }

        public void setDate(
            int parameterIndex,
            java.sql.Date x,
            java.util.Calendar cal)
            throws java.sql.SQLException {
            wrappedStatement.setDate(parameterIndex, x, cal);
            saveQueryParamValue(parameterIndex, x);
    }

        public void setInt(int parameterIndex, int x)
                        throws java.sql.SQLException {
                        wrappedStatement.setInt(parameterIndex, x);
            saveQueryParamValue(parameterIndex, new Integer(x));
        }

        public void setString(int parameterIndex, String x)
                        throws java.sql.SQLException {        
                        wrappedStatement.setString(parameterIndex, x);
            saveQueryParamValue(parameterIndex, x);
        }
       
//넘어온 데이터를 ArrayList에 담아주는 메서드입니다.
        private void saveQueryParamValue(int position, Object obj) {
                String strValue;
                if (obj instanceof String || obj instanceof Date) {
                        strValue = "'" + obj + "'";
                } else {
                        if (obj == null) {
                              strValue = "null";
                        } else {
                                strValue = obj.toString();
                        }
                }
                while (position >= parameterValues.size()) {
                parameterValues.add(null);
                }
                parameterValues.set(position, strValue);
        }
       
        //instance생성시 String에 넣어둔 query의 ‘?’를 ArrayList에 담긴 실제 데이터로
//치환해서 리턴해 줍니다.
        public String getQueryString() {
                //여기서 query를 String에도 담아준 이유는 webLogic의 jdk가 1.3 버전으로
//StringBuffer의 indexOf(String str) 메서드를 사용할 수 없었기 때문입니다.
//다른 방법이 있으시면 알려주세요..
                String sql = sqlTemplate;
                StringBuffer query = new StringBuffer(sqlTemplate);
                int idx = 0;
               
                if(!parameterValues.isEmpty())
                {
                        for(int i=1;i < parameterValues.size();i++)
                        {
                                idx = sql.indexOf("?");
                                query.replace(idx, idx+1, (String)parameterValues.get(i));
                                sql = query.toString();
                        }
                        parameterValues = null;
                        return query.toString();
                }
                else
                {
                        parameterValues = null;
                        return query.toString();
                }
        }
}


- 다음은 실제 출력문입니다.

[2005-11-23 13:50:19,030] INFO at pgw.database.listDAO.modify(listDAO.java:543)   -
========== llistDAO#modify#if Customer ==========
UPDATE ACKLIST
   SET NM_USER = 'aaaaaaaaaa',
       NO_SSN = '2222222222222',
       NO_MINHEADER = '222',
       NO_MINNUMBER = '22222222',
       REASON = '22222222222222222444444444444444444444',
       ID_MODIFY = 'pbadmin',
       DT_MODIFY = SYSDATE
WHERE SEQ_NUM = '460'
   AND TYPE = '2'

'프로그래밍 > Library' 카테고리의 다른 글

[펌] 가상주소 구현  (0) 2007.11.28
[펌] 404 Error  (0) 2007.11.28
[펌] JSP에서 원하는 Appender 선택하여 쓰기  (0) 2007.11.28
[펌] WEBLOGIC + LOG4J  (0) 2007.11.28
[펌] Log4J...  (0) 2007.11.28

JSP에서 원하는 Appender 선택하여 쓰기


참고로 이글의 원저자는 제가 아니므로 퍼가셔서 사용하실때 신중해주시기 바랍니다



만약 log4j 가 처음이라면 이 카테고리의 다음 포스트를 먼저 필독하세요


- log4j 웹에서 사용하기

- log4j 고급스럽게 사용하기

 

 

 

I. 먼저 log4j 프로퍼티 파일입니다

log4j.properties

log4j.rootLogger=INFO, stdout1, stdout2


log4j.logger.jsp1=INFO,stdout1
log4j.additivity.jsp1=false


log4j.logger.jsp2=INFO,stdout2
log4j.additivity.jsp2=false


log4j.appender.stdout1=org.apache.log4j.ConsoleAppender
log4j.appender.stdout1.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout1.layout.ConversionPattern=jsp1 appender log %d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n


log4j.appender.stdout2=org.apache.log4j.ConsoleAppender
log4j.appender.stdout2.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout2.layout.ConversionPattern=jsp2 appender log %d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n


log4j.logger.jsp1=INFO, stdout1
log4j.logger.jsp2=INFO, stdout2
jsp1과 jsp2의 두개의 logger를 정의합니다

jsp1 logger는 appender로 stdout1을 사용하며, jsp2 logger는 appender로 stdout2로 사용한다는 의미입니다


log4j.additivity.jsp1=false

additivity 속성은 jsp1 logger를 상위 로거(root logger)의 속성을 삭송받지 않겠다는 의미입니다

만약 이 속성이 없으면 동일한 메세지가 여러번 로깅될 것입니다

이하 속성은

http://www.jakartaproject.com/article/jakarta/1110438405982 등의 사이트를 참고하세요


II. JSP 샘플 소스

test_jsp1_appender.jsp

<%@ page contentType="text/html;charset=MS949"
 import="org.apache.log4j.Logger" %>

<%!
 static Logger logger1 = Logger.getLogger("jsp1");
%>

<%
logger1.warn("warn");
%>

로깅 메세지

jsp1 appender log2005-11-07 13:05:23,687 WARN  [http-8080-Processor5] jsp1 (test_jsp1_appender_jsp.java:48)     - warn


test_jsp2_appender.jsp

<%@ page contentType="text/html;charset=MS949"
 import="org.apache.log4j.Logger" %>

<%!
 static Logger logger2 = Logger.getLogger("jsp2");
%>

<%
logger2.warn("warn");
%>

로깅 메세지

jsp2 appender log2005-11-07 13:05:58,031 WARN  [http-8080-Processor4] jsp2 (test_jsp2_appender_jsp.java:48)     - warn


'프로그래밍 > Library' 카테고리의 다른 글

[펌] 404 Error  (0) 2007.11.28
[펌] Log4J 적용 사례(따라하기)  (0) 2007.11.28
[펌] WEBLOGIC + LOG4J  (0) 2007.11.28
[펌] Log4J...  (0) 2007.11.28
[펌] Log4j를 사용하여 Tomcat 5.x로깅 설정하기  (0) 2007.11.28

[펌] WEBLOGIC + LOG4J

프로그래밍/Library 2007. 11. 28. 09:38 Posted by galad
WEBLOGIC + LOG4J
조회(593)
Unix/Linux | 2007/05/11 (금) 12:11
공감하기 | 스크랩하기
<BASE target="_son">
log는 웹로직 7에서 작성되었다.

이전에 쓴 log모두다 weblogic 7을 기준으로 작성되었음을 밝힌다.
웹로직 8이상에서는 weblogic workshop 에서 log4j를 사용하고 있는듯하고
이것으로 충돌이 날수도 있다. 이런경우 bea사이트에서 도움을 받기 바란다.
문서로 적혀있는것을 언젠가 본적이 있는것 같다.
많은 사람들이 opensorce가 주제인 이 log들에 대해 왜 weblogic이냐고 묻는다. JBOSS 혹은 다른것도 몇개 있는데 말이다.
여기 있는 글중의 일부는 현업에서 충분히 쓰이고 있는 글을 다루다 보니
Weblogic 을 기준으로 적었지만 앞으로 open source WAS를 조금씩 바꿔가는 연습을 할것이다.

log4j에 관한 log를 상당히 오랫만에 적는다.
아무도 묻지도 않았기도 했지만...그동안 정신이 딴데 팔려서...


ps : 하지만 모든 WAS및 Tomcat 등의 servlet container에서도 잘 될듯하다. 아래의 원리를 안다면 말이다.



본론으로 들어가자.
1. log4j를 웹로직에서 쓰자.

웹로직에서의 log4j를 쓰는 방법에 대해서 많이 생각을 해봤다.
좀처럼 쉽지 않았음 T.T
원리는 간단한데 말이다.
일단 log4j의 특성을 알아야 한다.
이 log를 적기 위해 별별 opensoure를 뒤져야 했다. 몇몇 프레임웍및 opensource 혹은 상용소스들을 decompile해서 소스를 뒤졌다.
log4j를 이렇게 많이 쓰면서...
이렇게 자료가 없단말인가?

때문에 보다 객관적이고 정확한 방법으로 log를 쓰고 싶었다.
다른 분들도 보고 있기 때문에....
언제나 log를 쓰면서 느끼는건데 부끄럽다.



그 결론을 나름대로 내려보면
1. jdk1.4의 logging 기능을 함께 쓰는방법
2. apache common의 project를 이용해서 섞어쓰는 방법
3. log4j의 특성만 이용해서 사용하는 방법
위의 경우의 수중 내가 선택한방법은 3번이다.
그럼 3번을 쓰는 방법을 알아보자.

그냥 T.T
Jakarta Commons Logging은 java.util.logging 과의 연결 또는 Jakarta Log4j에 사용될 수 있다. Commons Logging은 로깅 추상 레이어로서 애플리케이션을 기저의 로깅 구현에서 고립시킬 수 있다. Commons Logging을 사용하여 설정 파일을 변경하여 기저의 로깅 구현을 바꿀 수 있다. Commons Logging은 Jakarta Struts 1.1과 Jakarta HttpClient 2.0에 사용된다.







2. 환경설정

1. C:beaweblogic700commonlib 아래에 log4j.jar 를 복사해 넣는다.
2. C:beauser_projectsmydomain 에서 startWeblogic.cmd를 열고
set JAVA_OPTIONS=-Dlog4j.config=log4j.xml -Dweblogic.security.SSL.trustedCAKeyStore=C:bea2weblogic700serverlibcacerts 와 같이 JAVA_OPTIONS을 수정한다.
물론 xml configuration및 properties파일은 startWeblogic.cmd가 있는 곳에 둬야 한다.

3. 이로서 환경 설정은 끝이다.
참고 : 만일 log4j.config파일을 사용하려면 위의 2의 Dlog4j.config=log4j.xml 을 Dlog4j.config=log4j.config 로 수정만 하면 된다.
이 예제에서는 xml를 사용하기로 했으니..xml예제를 한개 올린다.

log4j.xml
코드:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

   <appender name="cad" class="org.apache.log4j.RollingFileAppender">
      <!--<param name="File"   value="Logging4J.xml" />-->
      <param name="File"   value="Logging4J.log" />
      <param name="Append" value="true" />       
      <param name="MaxFileSize" value="1000"/>
      <param name="MaxBackupIndex" value="3"/>
     
     
      <!--<layout class="org.apache.log4j.xml.XMLLayout">
      </layout>-->
     
      <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern"
                value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
      </layout>           
       
   </appender>

   <root>
      <priority value ="debug" />
      <appender-ref ref="cad"/>
   </root>

</log4j:configuration>




3. 헉 정말로 저게 끝이단 말인가?

그져 코딩에(jsp, servlet, ejb 등등...) 아래의 예처럼 코딩만하면 된다.

코드:
package org.new21.lovelazur.test;

import org.apache.log4j.Logger;

public class LoveLazurLoggerTest
{
  private static Logger logger =
    Logger.getLogger(LoveLazurLoggerTest.class.getName());

  public void logerTest()
  {
      logger.debug("test logger ... best log system log4j!!");
  }

}



4. web.xml의 수정
환경 설정이 이것 뿐이라는 말은 t.t
하나 더 있기는 하다.
우선 web.xml을 열어 이렇게 수정한다.
코드:
<?xml version="1.0" ?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

  <!-- Loggin configuration -->
  <servlet>
    <servlet-name>Log4jInit</servlet-name>
    <servlet-class>org.new21.lovelazur.conf.Log4jInit</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>0</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

</web-app>



이건 뭐란말인가?
이것의 정체를 알려면 이전에 log에서 DomConfigurator를 찾아보기 바란다.
이것은 properties를 메모리에 로딩해서 범용적으로 쓰기 위함이다.
이제 이것의 servlet을 보도록 하자.

4. Config용 Servlet작성
org.new21.lovelazur.conf.Log4jInit

코드:
package org.new21.lovelazur.conf;

import javax.servlet.http.HttpServlet;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <p>Controls Log4j logging level.  Implemented as a servlet so
 * that logging levels can be adjusted by redeploying the webapp.
 * Log file is determined by "log4j.config" set as a system
 * property in the server startup file.</p>
 *
 * @author Copyright (c) 2003 by BEA Systems. All Rights Reserved.
 */
public class Log4jInit extends HttpServlet {

  public void init() {

    int debug = 0;
    String value;
    String logFile;

    logFile = System.getProperty("log4j.config");
    System.out.println("############### Log4J log file ##############" + logFile);
    value = getServletConfig().getInitParameter("debug");

    try {
       debug = Integer.parseInt(value);
    } catch (Throwable t) {
        debug = 0;
    }

    if (debug >= 1) {
      SimpleDateFormat formatter = new SimpleDateFormat("MMM d, yyyy H:mm:ss a z");
      Date today = new Date();
      String output = "<"+formatter.format(today)+"> <Debug> <love lazur>";
      System.out.println(output+" love lazur app log4j prop file: "+logFile);
    }

   
    if(logFile != null) {
      if (logFile.toString().toLowerCase().endsWith(".xml")) {
         System.out.println("############### Log4J DOMConfigurator configure ##############" );
         DOMConfigurator.configure(logFile);
      } else {
         System.out.println("############### Log4JPropertyConfigurator configure ##############" );   
         PropertyConfigurator.configure(logFile);
      }
    }
   
  }
}


이것을 compile해서 WEB-INF 아래에 넣어주면 된다.


5. log확인 T.T

이것을 잘 따라했다면...아래와 같은 로그를 볼수 있다.
로그 파일 이름은 log4j.xml에 기술한 Logging4J.log 이다. 그리고 이 로그는 주기적으로 backup을 받으므로 착오없길 바란다.
2004-02-26 13:31:29,578 DEBUG [ExecuteThread: '11' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 13:31:29,609 DEBUG [ExecuteThread: '11' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 13:31:29,640 DEBUG [ExecuteThread: '11' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 14:05:06,656 DEBUG [ExecuteThread: '12' for queue: 'default'] test.LoveLazurLoggerTest (LoveLazurLoggerTest.java:12) - test logger ... best log system log4j!!
2004-02-26 15:10:03,453 DEBUG [ExecuteThread: '12' for queue: 'default'] calculation.BonusCalculatorBean (BonusCalculatorBean.java:21) - <<<<<<< test EJB logger ... best log system log4j!! >>>>>>>



6. 잡소리 T.T
LOG4J의 Weblogic에서의 설정을 마치고 사용법도 익혔다.
만일 tomcat및 다른 container에서 사용하려면 class path(tomcat의 경우는 xxx/yyy/lib 인데 기억안나지만 그곳에 log4j.jar를 넣어두면 된다.
그리고 web.xml및 Log4jInit.class 을 적절한 위치에 넣어두면 된다.
아마 다 될것이라고 믿는다.

[펌] Log4J...

프로그래밍/Library 2007. 11. 28. 09:38 Posted by galad

/**
 * Created on 2004. 3. 05.
 *
 *
 * @author 짱가
 *
 * *******************************************************
 *                             코드수정히스토리
 * 날짜                               작업자                      내용
 * 2004. 3. 3.                       짱가
 * *******************************************************
 *
 */
/**
 * System.out.println("[WARN]  : " +  e.toSting()  );
 * 이런식으로 코딩해본적 있을것이다. 혹시라도 로그를 빼달라고 하면 모두들
 * System.out.println("[WARN] : " +  e.toSting()  ); //라고 해본사람들이 꽤될것이다.
 * 컴파일 했다가 말았다가 어느게 시스템 Debug용이고 어느게 뭔지 .....
 * 프로그래밍이 끝나고 운용에 들어가면서 속도 향상을 위해 클라이언트가 FATAL에러만 빼고 모두 빼달라고 했다던지
 * 혹은 운용중에 에러를 잡기위해 어느단계까지는 모두 나오게 해야한다던지 할때 이런기능이 없다면
 * 아마 소스코드는 IF문과 System.out.println() 으로 뒤범벅이 될것이다.
 *
 *
 * 로그레벨이라는 개념을 사용하면 약간 수고를 덜수 있다.
 * DEBUG < INFO < WARN < ERROR < FATAL
 * 보통 로그level에서 DEBUG가 가장작고 FATAL이 가장크다. 그래서 위의 예제 결과는 아래와 같이 나온다.
 * 결과를 봐서 알겠지만 예제에서 WORN을 LogLevel로 삼았기 때문에 위의 그림과 같게 아래의 결과가 나온다.
 * java.lang.Object
 *|
 * +--org.apache.log4j.Category
 *       |
 *       +--org.apache.log4j.Logger
 * 보는 바와 같이 Logger class는 Category의 child class였다.
 *
 *
 *
 *
 * API의 일부....
 *
 * Constructor Summary
 * protected Logger(String name)
 *
 * Method Summary
 *
 * 1) static Logger getLogger(Class clazz) --- Same as calling getLogger(clazz.getName()).
 *
 * 2) static Logger getLogger(String name) --- Retrieve a logger by name.
 *
 * 3) static Logger getLogger(String name, LoggerFactory factory) ---Like getLogger(String)
 *     except that the type of logger instantiated depends on the type
 *     returned by the LoggerFactory.makeNewLoggerInstance(java.lang.String) method of the factory parameter.
 *
 * 4) static Logger getRootLogger() --- Retrieve the root logger. 
 *
 * ----------------------------------------------------------------------------------------------------
 * 1)과 2번은 클라스로 근본적으로 같고 예를들면 Logger.getLogger( xxxx.class.getName( ) )
 * 이런식으로 쓰므로 1번과 2번은 근본적으로 같다고 볼수 있다.
 * 3)은 LoggerFactory에 의해 불리우는것에 따라 logger type이 달라지고....
 * 4)째는 모든 Logger는 부모가 있는데 이 부모logger를 사용가능하지만 별로 권장하지 않는다고 한다.
 * http://www.onjava.com/lpt/a/2525 을 참조하기 바란다.
 * 이글에 보면 Tomcat에서의 사용법까지 나와있으며 어떻게 로그를 남기는것이 효율적인지에 대하여 적고 있다.
 *--------------------------------------------------------------------------------------
 *
 * logger class는 Category 클라스의 setLevel( )을 호출하게 되면 level이 정해진다.
 * http://logging.apache.org/log4j/docs/api/org/apache/log4j/Level.html 에서 보는바와같이 level의 종류는
 * 코드:
 * static Level ALL
 * static Level DEBUG
 * static Level ERROR
 * static Level FATAL
 * static Level INFO
 * static Level OFF
 * static Level WARN 와같다.
 * http://logging.apache.org/log4j/docs/api/org/apache/log4j/Category.html
 * 실제 로그를 뿌리는 메서드들을 발견할수 있을것이다. 
 *

 * Log4j 는 기본적으로 다섯개의 우선권(이하 Priority) 등급으로 메세지를 로깅할 수 있다.
 * 1. 완성된 어플리케이션에서는 출력되지 않아야 할 디버깅 메세지들을 쓰기위해 debug 를 사용하라.
 * 2. 어플리케이션의 verbose 모드에서 출력될만한 메세지들을 로깅하기 위해 info 를 사용하라.
 * 3. 어플리케이션이 이상없이 계속 실행될 수 있는 정도의 경고메세지를 로깅하기 위해 warn 을 사용하라.
 * 4. 어플리케이션이 그럭저럭 돌아갈만한 정도의 에러베세지를 로깅하기 위해 error 를 사용하라.
   예를들어 관리자에 의해 주어진 설정인자가 올바르지 않아 하드코딩된 기본값을 사용해야 할 경우.
 * 5. 로깅후에 애플리케이션이 비정상적으로 종료될 치명적인 메세지를 로깅하기 위해 fatal 을 사용하라.

 *****************************************************************************
 *
 *  여기서 작성한 클래스는 각 기능 별로 txt로 생성하는 로그
 *                                          html로 생성하는 로그
 *           pattern matching을 사용하는 로그
 *            xml로 생성하는 로그
 *           그리고 configuration file로 xml을 사용하여 log파일로 생성하고
 *           그 log파일이 일정 사이즈가 될때 증가시키는 로그로 나뉘어져 있다
 *  참고로 각 실행시에 main으로 실행한후 메소드 명만 수정하였으므로 실행은 그렇게 하면 실행이 가능하다.
 *
 *****************************************************************************
 *
 */

import java.io.FileOutputStream;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.HTMLLayout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.xml.DOMConfigurator;
import org.apache.log4j.xml.XMLLayout;

public class Logging4J {

 //  Logger.getInstance()의 실체는 Category.getInstance()이며,
 //  Category가 반환되기때문이다. 그리고 이 Category 클래스나 getInstance()
 //     메소드는 추천 되지 않는 API인것으로 알려져있다.
 //  Logger의 취득은 Logger.getLogger()로 하기 바란다
 static Logger logger = Logger.getLogger(Logging4J.class);

 public void consoleLog() {

  Logger logger = Logger.getLogger("loggerTest");
  BasicConfigurator.configure();
  logger.setLevel(Level.WARN);
  logger.debug("this is debug!!!");
  logger.info("this is info!!!");
  logger.warn("this is warn!!!");
  logger.error("this is error!!!");
  logger.fatal("this is fatal!!!");

 }
 /**
  * 생성 로그  txt
  * DEBUG - debug!!!
  * INFO - info!!!
  * WARN - warn!!!
  * ERROR - error!!!
  * FATAL - fatal!!!     *
  */
 public void txtLog() {

  SimpleLayout layout = new SimpleLayout();

  FileAppender appender = null;
  try {
   appender = new FileAppender(layout, "Logging4J.txt", false);
  } catch (Exception e) {
  }

  //  FileAppender(Layout layout, String filename)  : constructor
  //  FileAppender(Layout layout, String filename, boolean append)

  logger.addAppender(appender);

  logger.setLevel(Level.DEBUG);
  logger.debug("debug!!!");
  logger.info("info!!!");
  logger.warn("warn!!!");
  logger.error("error!!!");
  logger.fatal("fatal!!!");
 }
 // html은 너무 길다.. ^^;;
 public void htmlLog() {

  HTMLLayout layout = new HTMLLayout();

  WriterAppender appender = null;
  try {
   FileOutputStream output = new FileOutputStream("Logging4J.html");
   appender = new WriterAppender(layout, output);
  } catch (Exception e) {
  }

  //  FileAppender(Layout layout, String filename)  : constructor
  //  FileAppender(Layout layout, String filename, boolean append)
  //  Logger.getInstance()의 실체는 Category.getInstance()이며,
  //  Category가 반환되기때문이다. 그리고 이 Category 클래스나 getInstance() 메소드는
  //     추천 되지 않는 API인것으로 알려져있다.
  //  Logger의 취득은 Logger.getLogger()로 하기 바란다

  logger.addAppender(appender);

  logger.setLevel(Level.DEBUG);
  logger.debug("HERE is some Debug!!!");
  logger.info("HERE is some info!!!");
  logger.warn("HERE is some WARN!!!");
  logger.error("HERE is some ERROR!!!");
  logger.fatal("HERE is some FATAL!!!");
 }
 /**
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:194)
  * 메시지 : Here is some DEBUG
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:195)
  * 메시지 : Here is some INFO
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:196)
  * 메시지 : Here is some WARN
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:197)
  * 메시지 : Here is some ERROR
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:198)
  * 메시지 : Here is some FATAL
  *
  *
  */
 public void patternLog() {
  // Note, %n is newline
  String pattern = "Classname: %C %n";
  pattern += "Date in ISO8601 format: %d{ISO8601} %n";
  pattern += "이벤트 위치: %l %n";
  pattern += "메시지 : %m %n %n";

  PatternLayout layout = new PatternLayout(pattern);
  ConsoleAppender appender = new ConsoleAppender(layout);

  logger.addAppender(appender);
  logger.setLevel((Level) Level.DEBUG);

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }
 // xml로 기록
 public void xmlLog() {
  XMLLayout layout = new XMLLayout();

  //option setting
  layout.setLocationInfo(true);

  FileAppender appender = null;
  try {
   appender = new FileAppender(layout, "Logging4J.xml", false);
  } catch (Exception e) {
  }

  logger.addAppender(appender);
  logger.setLevel((Level) Level.DEBUG);

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }

 //--------참고.    http://www.vipan.com/htdocs/log4jhelp.html  --------//
 // for using xml conf

 // properties를 이용하는 것은 다루지 않았다.

 /**
  * conf file
  * <?xml version="1.0" encoding="UTF-8" ?>
  * <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  *
  * <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  *
  *    <appender name="cad" class="org.apache.log4j.FileAppender">
  *       <param name="File"   value="Logging4J.xml" />
  *       <param name="Append" value="false" />
  *       <layout class="org.apache.log4j.xml.XMLLayout"/>
  *    </appender>
  *
  *    <root>
  *       <priority value ="debug" />
  *       <appender-ref ref="cad"/>
  *    </root>
  *
  * </log4j:configuration>
  *
  */
 public void confLog() {
  //dom conf start
  String conf = "log4jconf.xml";
  DOMConfigurator.configure(conf);
  //dom conf end

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }
 //LollingFileAppender
 //log4j가 쌓는 로그의 순서
 //최근것은 항상 Logging4J.log 에 쌓이고 이것이 예에서 1000바이트가 차들어가면
 //Logging4J.log를 복사하여
 //Logging4J.log.1을 만들고 Logging4J.log에는 최근게 계속 쌓인다.
 /**
  * <?xml version="1.0" encoding="UTF-8" ?>
  * <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  *
  * <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  *
  *    <appender name="cad" class="org.apache.log4j.RollingFileAppender">
  *       <!--<param name="File"   value="Logging4J.xml" />-->
  *       <param name="File"   value="Logging4J.log" />
  *       <param name="Append" value="true" />
  *       <param name="MaxFileSize" value="1000"/>
  *       <param name="MaxBackupIndex" value="3"/>
  *
  *
  *       <!--<layout class="org.apache.log4j.xml.XMLLayout">
  *       </layout>-->
  *
  *       <layout class="org.apache.log4j.PatternLayout">
  *          <param name="ConversionPattern"
  *                 value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
  *       </layout>
  *
  *    </appender>
  *
  *    <root>
  *       <priority value ="debug" />
  *       <appender-ref ref="cad"/>
  *    </root>
  *
  * </log4j:configuration>
  */
 public void lollingLog() {
  //dom conf start
  String conf = "log4jconfLolling.xml";
  DOMConfigurator.configure(conf);
  //dom conf end

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }

 // pattern layout
 // --참고 -- http://logging.apache.org/log4j/docs/api/org/apache/log4j/PatternLayout.html
 /**
  * 출력패턴
  *
  * * ------+-------------------------------------------------------------------------------
 * %c    | 로그 이벤트의 카테고리명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %C    | 로깅 요구를 실시하는 클래스명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %d    | 로그 이벤트의 일시를 출력한다.
 *       | %d{HH:mm:ss} 나 %d{dd MMM yyyy HH}로서보다 유연하게 일시 정보를 출력할 수가 있다. 
 * ------|-------------------------------------------------------------------------------
 * %F(*) | 로그 요구가 발생한 파일명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %l(*) | 로그가 생성되었을 때에 불려 간 위치(소스명, 행)를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %L(*) | 로깅 요구를 행한 행 번호를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %m    | 로깅이벤트로 설정된 메세지를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %M(*) | 로그 요구가 행해진 메소드명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %n    | 플랫폼 의존의 개행 문자를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %p    | 로그의 우선도를 출력합니다. 
 * ------|-------------------------------------------------------------------------------
 * %r    | 어플리케이션이 개시하고 나서, 로그가 출력될 때까지의 시간을 밀리 세컨드 단위로 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %t    | 로그를 생성한 thread의 이름을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %x    | 로그가 생성된 thread의 NDC(네스트화 진단 문맥)를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %%    | %를 출력한다. 
 * ------+-------------------------------------------------------------------------------
 *
 *(*)이것들을 출력할 때의 퍼포먼스는 좋지 않기 때문에, 어플리케이션의 실행 속도가 문제가 되지 않는 경우에게만
 *사용하는 것이 추천 되고 있다
 *
 *
 ** 여러가지 출력포멧 : 위의 xml에 아래의 포멧들을 적용해서 이것저것 찍어보도록 하자코드:
 * ----------+-------------------------------------------------------------------------
 * %10m      | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 좌측으로 공백이 삽입된다. 
 * ----------+-------------------------------------------------------------------------
 * %-10m     | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 우측으로 공백이 삽입된다. 
 * ----------+-------------------------------------------------------------------------
 * %.10m     | 출력 캐릭터 라인이 10 문자를 넘는 경우,
 *           | 캐릭터 라인의 오른쪽으로부터 세어 11 문자눈 이후의 문자(선두의 문자)가 잘라내진다. 
 * ----------+-------------------------------------------------------------------------
 * %10.20m   | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 좌측으로 공백이 삽입된다.
 *           | 출력 캐릭터 라인이 20 문자를 넘는 경우,
 *           | 캐릭터 라인의 오른쪽으로부터 세어
 *           | 21 문자 이후의 문자(선두의 문자)가 잘라내진다. 
 * ----------+-------------------------------------------------------------------------
 * %-10.20m  | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 우측으로 공백이 삽입된다.
 *           | 출력 캐릭터 라인이 20 문자를 넘는 경우,
 *           | 캐릭터 라인의 오른쪽으로부터 세어 21 문자이후의 문자(선두의 문자)가 잘라내진다. 
 * ----------+-------------------------------------------------------------------------
 *
 *
 *  패턴 layout을 쓰는 예제 xml conf 파일코드:
 * <?xml version="1.0" encoding="UTF-8" ?>
 * <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 *
 * <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 *
 *    <appender name="cad" class="org.apache.log4j.RollingFileAppender">
 *       <!--<param name="File"   value="Logging4J.xml" />-->
 *       <param name="File"   value="Logging4J.log" />
 *       <param name="Append" value="true" />       
 *       <param name="MaxFileSize" value="1000"/>
 *       <param name="MaxBackupIndex" value="3"/>
 *      
 *      
 *       <!--<layout class="org.apache.log4j.xml.XMLLayout">
 *       </layout>-->
 *      
 *       <layout class="org.apache.log4j.PatternLayout">
 *          <param name="ConversionPattern"
 *                 value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
 *       </layout>           
 *       
 *    </appender>
 *
 *    <root>
 *       <priority value ="debug" />
 *       <appender-ref ref="cad"/>
 *    </root>
 *
 * </log4j:configuration> 
  */
 
 public static  void main(String[] args) {
   //dom conf start
   String conf = "log4jPattern.xml";
   DOMConfigurator.configure(conf);
   //dom conf end

   logger.debug("Here is some DEBUG");
   logger.info("Here is some INFO");
   logger.warn("Here is some WARN");
   logger.error("Here is some ERROR");
   logger.fatal("Here is some FATAL");
  }
/**
 *  결과
 * 2004-03-04 11:27:57,038 DEBUG [main] Logging4J (Logging4J.java:440) - Here is some DEBUG
 * 2004-03-04 11:27:57,038 INFO  [main] Logging4J (Logging4J.java:441) - Here is some INFO
 * 2004-03-04 11:27:57,038 WARN  [main] Logging4J (Logging4J.java:442) - Here is some WARN
 * 2004-03-04 11:27:57,038 ERROR [main] Logging4J (Logging4J.java:443) - Here is some ERROR
 * 2004-03-04 11:27:57,038 FATAL [main] Logging4J (Logging4J.java:444) - Here is some FATAL
 */
 
}

Log4j를 사용하여 Tomcat 5.x로깅 설정하기

알림 : 여기의 방법은 Tomcat에 log4j로깅을 셋팅하기 위한것이다. 당신의 애플리케이션은 해당 웹 애플리케이션만의 log4j.properties를 WEB-INF/classes디렉토리로 배치될수 있다.

$CATALINA_HOME/common/classes에 log4j.properties파일을 생성한다.

log4j.rootLogger=ERROR, R
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File=${catalina.home}/logs/tomcat.log
log4j.appender.R.DatePattern='.'yyyy-MM-dd
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern={%p} %c{2} %x %m%n

commons-logging.jar 와 log4j-1.2.9.jar을 $CATALINA_HOME/common/lib로 복사할 필요가 있다. 주의, 몇몇 참조문서에서는 $CATALINA_HOME/server/lib로 이 파일을 복사해야 한다고 말하고 있으나 나의 경우 common/lib로 복사하는 것이 잘 작동했다.

그리고 나서, 당신의 애플리케이션 log4j.properties($APPFUSE_HOME/web/WEB-INF/classes)에 다음의 내용을 추가한다.

# Suppress the tomcat logging whilst DEBUG is switched on
log4j.logger.org.apache.catalina.core=ERROR
log4j.logger.org.apache.catalina.session=ERROR
log4j.logger.org.apache.jasper.compiler=ERROR

개발하는 동안, 당신은 root logger를 DEBUG로 셋팅한다. 그리고 나서 Tomcat로깅이 DEBUG로 셋팅이 되면, 당신은 Jasper컴파일러로부터 수천라인의 컴파일 추적메시지를 보게될것이다.

물론, 특정 애플리케이션을 구별하여, 당신의 애플리케이션 log4j.properties ROOT로그 레벨을 WARN이나 ERROR로 유지하고 특정 애플리케이션을 위해 디버깅을 활성화한다.(이를테면, org.appfuse=DEBUG)

노트: 만약 당신이 stdout으로 Tomcat로깅을 수행하길 원한다면, 당신의 $CATALINA_HOME/common/classes/log4j.properties파일은 다음과 같을것이다.

log4j.rootLogger=ERROR, stdout, TOMCAT

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{dd-MM-yy HH:mm:ss:SSS} - {%p} %c{2} Thread [%t]; %x %m%n

log4j.appender.TOMCAT=org.apache.log4j.DailyRollingFileAppender
log4j.appender.TOMCAT.File=${catalina.home}/logs/tomcat.log
log4j.appender.TOMCAT.DatePattern='.'yyyy-MM-dd
log4j.appender.TOMCAT.layout=org.apache.log4j.PatternLayout
log4j.appender.TOMCAT.layout.ConversionPattern=%d{dd-MM-yy HH:mm:ss:SSS} - {%p} %c{2} Thread [%t]; %x %m%n

'프로그래밍 > Library' 카테고리의 다른 글

[펌] WEBLOGIC + LOG4J  (0) 2007.11.28
[펌] Log4J...  (0) 2007.11.28
[펌] log4j 설정법  (0) 2007.11.28
[펌] Log4J Configuration  (0) 2007.11.28
[펌] Log4J 사용하기  (0) 2007.11.28

[펌] log4j 설정법

프로그래밍/Library 2007. 11. 28. 09:37 Posted by galad
[펌]log4j 설정법
참고파일



=============================================

본문서는 자유롭게 배포/복사 할수 있지만

이문서의 저자에 대한 언급을 삭제하시면 안됩니다

저자 : GoodBug (unicorn@jakartaproject.com)

최초 : http://www.jakartaproject.com 

=============================================


LOG4J

I. 들어가면서.. 그리고 log4j


log4j는 자바 어플리케이션에서 빠르고 효과적으로 로깅 할 수 있도록 도와주는 오픈 소스 프로젝트입니다.


로깅(logging)은 코드의 가독성을 떨어뜨리는 단점이 있지만 애플리케이션에 문제가 있을 때 개발자가 자세한 상황을 파악할 수 있도록 해 주며 테스팅시 빠질 수 없는 요소입니다.


아마도 여러분들은 여러 어플리케이션이 추가되면서 각 개발자들만의 독특한 로깅방식이 서로 썩이고 얽혀서 화면에 나타나는것을 많이 봤을겁니다 -_-;
즉 로깅방법을 통일할 필요가 있는것이죠. 모든 개발자가 특정 포맷에 맞추어서 로깅 한다면 한결 로깅하기도 편하겠지요


오픈 소스 프로젝트인 Log4j는 개발자들이 매우 손쉽고 다양한 형태로 로깅을 할 수 있도록 도와줍니다. 성능또한 우수해 더이상 System.out.println을 사용할 필요가 없습니다.



II. 다운로드


다운로드 http://logging.apache.org/log4j/docs/download.html

매뉴얼 http://logging.apache.org/log4j/docs/documentation.html

API spec http://logging.apache.org/log4j/docs/api/index.html



III. LOG4J 구조


일단 log4j를 잘 모르지만 그 구조만 살짝 살펴보고 넘어갑시다

log4j는 크게 3가지 요소로 구성되며 그 구조는 다음과 같습니다

① Logger(Category) : 로깅 메세지를 Appender에 전달합니다.

② Appender : 전달된 로깅 메세지를 파일에다 기록할 것인지, 콘솔에 출력할 것인지

                   아니면 DB에 저장할 것인지 매개체 역활을 합니다.

③ Layout : Appender가 어디에 출력할 것인지 결정했다면 어떤 형식으로 출력할 것이지

                출력 layout을 결졍합니다.

쉽죠?



IV. LOG4J 로깅 레벨


log4j는 다양한 로깅레벨을 지원합니다.


① FATAL : 가장 크리티컬한 에러가 일어 났을 때 사용합니다.

② ERROR : 일반 에러가 일어 났을 때 사용합니다.

③ WARN : 에러는 아니지만 주의할 필요가 있을 때 사용합니다.

④ INFO : 일반 정보를 나타낼 때 사용합니다.

⑤ DEBUG : 일반 정보를 상세히 나타낼 때 사용합니다.


만약 로깅 레벨을 WARN 으로 설정하였다면 그 이상 레벨만 로깅하게 됩니다.

즉 WARN, ERROR, FATAL 의 로깅이 됩니다.



V. 샘플코드 1


jsp에서 사용하는 예제가 없어 만들어 봤습니다.


test.jsp


<%@ page contentType="text/html;charset=MS949"
 import="org.apache.log4j.Logger" %>

<%!
 static Logger logger = Logger.getLogger("test.jsp");
%>

<%
logger.fatal("fatal!!");

logger.fatal("fatal2!!", new NullPointerException("널입니다요"));

logger.error("error!", new NumberFormatException());

logger.error("error!2");

logger.warn("warn");

logger.info("info");

logger.debug("debug");
%>


결과 콘솔화면








static Logger logger = Logger.getLogger("test.jsp");

static 메소드 getLogger를 통해 logger 인스턴스를 가져옵니다.
getLogger에는 파라미터로 스트링 혹은 클래스를 사용하는데 jsp에서는 클래스를 파라미터로 주기에는 좀 애매합니다. 그냥 스트링으로 주도록 하지요


logger.fatal("fatal!!");
logger.fatal("fatal2!!", new NullPointerException("널입니다요"));
  
logger에 fatal 레벨의 메세지를 전달합니다. 다음 두가지 메소드를 지원하는군요

fatal(Object message)

fatal(Object message, Throwable t)

각 레벨마다 위처럼 두가지 메소드를 지원합니다.


지원 메쏘드
logger.fatal(Object message) logger.fatal(Object message, Throwable t)
logger.error(Object message) logger.error(Object message, Throwable t)
logger.warn(Object message) logger.warn(Object message, Throwable t)
logger.info(Object message) logger.info(Object message, Throwable t)
logger.debug(Object message) logger.debug(Object message, Throwable t)


VI. 샘플코드 2


서블릿의 경우 다음과 같이 코딩하면 되겠군요

TestServlet.java


import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class TestServlet extends HttpServlet {


    static Logger logger = Logger.getLogger(TestServlet.class);


    public void init(ServletConfig config) throws ServletException {
         super.init(config);
    }


    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         try {
              ...
   
              logger.info("Hellow World~");

              ...

          } catch (Exception e) {
              logger.error("Error at TestServlet", e);
          }
     }
}



VII. LOG4J 설정


log4j 설정은 프로그램 내에서 할 수 있지만 설정파일을 사용함으로서 좀더 유연하게 log4j환경을 만들 수 있습니다.


프로그램에서 설정

<%@ page contentType="text/html;charset=MS949"
 import="org.apache.log4j.*,java.io.* "
%>

<%!
 static Logger logger = Logger.getLogger("log4j.jsp");
%>

<%
String layout = "%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n";
String logfilename = "DailyLog.log";
String datePattern = ".yyyy-MM-dd ";

PatternLayout patternlayout = new PatternLayout(layout);
DailyRollingFileAppender appender = new DailyRollingFileAppender(patternlayout, logfilename, datePattern);
logger.addAppender(appender);
logger.setLevel(Level.INFO);
logger.fatal("fatal!!");
%>


property 파일에 설정
log4j.properties를 만들어 /WEB-INF/classes 밑에 놓으세요



log4j.rootLogger=INFO, stdout, rolling

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender

log4j.appender.rolling.File=output.log

log4j.appender.rolling.Append=true

log4j.appender.rolling.MaxFileSize=500KB

log4j.appender.rolling.DatePattern='.'yyyy-MM-dd

log4j.appender.rolling.layout=org.apache.log4j.PatternLayout

log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n


#최상위 카테고리에 INFO로 레벨 설정 및 appender로 stdout, rolling을 정의

log4j.rootLogger=INFO, stdout, rolling

#stdout 어펜더는 콘솔에 뿌리겠다는 정의

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#stdout 어펜더는 patternlayout을 사용하겠다는 정의

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

#페턴은 다음과 같이 포맷팅 하겠다는 것을 정의

log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n


#역시나 rolling 어펜더는 파일로 처리한다라고 정의

log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender

#로그 파일 이름은 output.log

log4j.appender.rolling.File=output.log

#true면 톰캣을 내렸다 올려도 파일이 리셋되지 않습니다.

log4j.appender.rolling.Append=true

#파일 최대 사이즈는 500KB로 설정

log4j.appender.rolling.MaxFileSize=500KB

#파일 포맷은 output.log.2005-03-10 으로 관리하겠다고 정의

log4j.appender.rolling.DatePattern='.'yyyy-MM-dd

#역시나 rolling 어펜더는 패턴 레이아웃을 사용하겠다고 정의

log4j.appender.rolling.layout=org.apache.log4j.PatternLayout

#rolling 어펜더는 패턴 레이아웃 포맷

log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n



VIII. 설정 포맷


로그파일명 포맷 (DatePattern)
로그파일명 포맷입니다. 날짜, 시간 및 분단위로까지 로그 파일을 분리할 수 있습니다.

형식 설명
'.'yyyy-MM 매달 첫번째날에 로그파일을 변경합니다
'.'yyyy-ww 매주의 시작시 로그파일을 변경합니다.
'.'yyyy-MM-dd 매일 자정에 로그파일을 변경합니다.
'.'yyyy-MM-dd-a 자정과 정오에 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH 매 시간의 시작마다 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH-mm 매분마다 로그파일을 변경합니다.



PatternLayout 포맷
로그자체를 어떤 포맷으로 남길지 결정합니다.
layout에는 HTMLLayout, PatternLayout, SimpleLayout, XMLLayout등이 있으며 PatternLayout이 일반적으로 가장 많이 쓰입니다.


형식 설명
%p debug, info, warn, error, fatal 등의 priority 가 출력된다.
%m 로그내용이 출력됩니다
%d 로깅 이벤트가 발생한 시간을 기록합니다.
포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
%t 로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
%% % 표시를 출력하기 위해 사용한다.
%n 플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.
%c 카테고리를 표시합니다
예) 카테고리가 a.b.c 처럼 되어있다면 %c{2}는 b.c가 출력됩니다.
%C 클래스명을 포시합니다.
예) 클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면 %C{2}는 xyz.SomeClass 가 출력됩니다
%F 로깅이 발생한 프로그램 파일명을 나타냅니다.
%l 로깅이 발생한 caller의 정보를 나타냅니다
%L 로깅이 발생한 caller의 라인수를 나타냅니다
%M 로깅이 발생한 method 이름을 나타냅니다.
%r 어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
%x 로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를 출력합니다.
%X 로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를 출력합니다.

예시) (같은 색끼리 보시면 됩니다)

위의 test.jsp를 다음 포맷으로 출력해본다면

[%c] [%C] [%d] [%F] [%l] [%L] [%m] [%M] [%n] [%p] [%r] [%t] [%x] [%X]는 다음과 같다

[test.jsp] [org.apache.jsp.test_jsp] [2005-03-10 12:37:23,561] [test_jsp.java] [org.apache.jsp.test_jsp._jspService(test_jsp.java:64)] [64] [fatal!!] [_jspService] [개행] [FATAL] [765567] [http-8080-Processor25] [] []

'프로그래밍 > Library' 카테고리의 다른 글

[펌] Log4J...  (0) 2007.11.28
[펌] Log4j를 사용하여 Tomcat 5.x로깅 설정하기  (0) 2007.11.28
[펌] Log4J Configuration  (0) 2007.11.28
[펌] Log4J 사용하기  (0) 2007.11.28
[펌] Tomcat의 Logger(로거)가 이상해요..  (0) 2007.11.28

[펌] Log4J Configuration

프로그래밍/Library 2007. 11. 28. 09:37 Posted by galad
#####################################
#  Log4J Configuration                       #
#####################################

작성자 : 장형화(hhjang97@venus.uos.ac.kr)
작성일 : 2005. 08. 11
수정일 :

원본 :
설명 :


################################# ################################# #################################

LOG4J !!

디버그나 에러 확인을 위해 항상 사용하는 API입니다.

다양한 Level로 나눠서 설정 가능하며, System.out.println을 난발(?) 하지 않도록 도와주고 있죠 ^^;



설정 부분에서 필요한 부분을 그때그때 웹사이트 돌아다니며 찾았는데,

여기에 포스트 해놓고 필요할때 찾아 쓰려고요 ^^;



!-----------------------------------------------------------------------------!
! category(logger) 설정                                                                                    !
!-----------------------------------------------------------------------------!
log4j.debug=true
#log4j.disable=INFO
log4j.rootLogger=DEBUG, CONSOL, SYSTEM





!-----------------------------------------------------------------------------!
! appender(log destinations/targets) 와 옵션들을 설정                                         !
!-----------------------------------------------------------------------------!

! FILE에 로그 쓰기, 지정한 크기를 넘어서면 파일을 교체
log4j.appender.CONSOL=org.apache.log4j.ConsoleAppender


! 로그메세지들이 전혀 버퍼되지 않는 것을 의미하며 대부분의 상황에 적당하다.
log4j.appender.CONSOL.ImmediateFlush=true


! 이 appender 는 여기 명시된 priority 와 같거나 높은 메세지만 로깅한다
log4j.appender.CONSOL.Threshold=DEBUG


! Layout 형식 : TTCCLayout, HTMLLayout,  XMLLayout, PatternLayout, SimpleLayout
! PatternLayout, SimpleLayout - 자바의 Throwable 에러들과 예외를 무시한다
log4j.appender.CONSOL.layout=org.apache.log4j.PatternLayout


! %l - 소스코드의 위치정보를 출력한다. %C. %M(%F:%L) 의 축약형이다
log4j.appender.CONSOL.layout.ConversionPattern=%-5p %l %x =>%m%n







log4j.appender.SYSTEM=org.apache.log4j.DailyRollingFileAppender
log4j.appender.SYSTEM.File=C:/logs/system.html


! 매일 자정에 로그파일을 교체하며 기존파일은 xx.log_2004.07.12
log4j.appender.SYSTEM.DatePattern='.'yyyy-MM-dd
log4j.appender.SYSTEM.Threshold=DEBUG


! 자바의 Throwable 에러들과 예외를 포함하기 위해 HTMLLayout을 사용한다.
log4j.appender.SYSTEM.layout=org.apache.log4j.HTMLLayout


! [%d{yyyy-MM-dd}형식은 프로그램의 실행속도를 느리게 함으로 SimpleDateFormat 형식지정한다.
log4j.appender.SYSTEM.layout.DateFormat=ISO8601
! [YYYY-MM-DD HH:MM:SS, mm] 형식을 뜻한다.
log4j.appender.SYSTEM.layout.TimeZoneID=GMT-8:00


! %l - 소스코드의 위치정보를 출력한다. %C. %M(%F:%L) 의 축약형이다
log4j.appender.SYSTEM.layout.ConversionPattern=[%d] %-5p %l - %m%n







! 각 업무 상위패키지를 지정하여 logging 셋팅한다.


log4j.logger.rkhwang.biz.protoss=DEBUG, PROTOSS
log4j.logger.rkhwang.biz.teran=DEBUG, TERAN
log4j.logger.rkhwang.biz.zerg=DEBUG, ZERG





log4j.appender.PROTOSS=org.apache.log4j.DailyRollingFileAppender
log4j.appender.PROTOSS.File=C:/logs/protoss.log
log4j.appender.PROTOSS.DatePattern='.'yyyy-MM-dd
log4j.appender.PROTOSS.Threshold=DEBUG
log4j.appender.PROTOSS.layout=org.apache.log4j.PatternLayout
log4j.appender.PROTOSS.layout.DateFormat=ISO8601
log4j.appender.PROTOSS.layout.TimeZoneID=GMT-8:00
log4j.appender.PROTOSS.layout.ConversionPattern=[%d] %-5p %l - %m%n





log4j.appender.TERAN=org.apache.log4j.DailyRollingFileAppender
log4j.appender.TERAN.File=C:/logs/teran.log
log4j.appender.TERAN.DatePattern='.'yyyy-MM-dd
log4j.appender.TERAN.Threshold=DEBUG
log4j.appender.TERAN.layout=org.apache.log4j.PatternLayout
log4j.appender.TERAN.layout.DateFormat=ISO8601
log4j.appender.TERAN.layout.TimeZoneID=GMT-8:00
log4j.appender.TERAN.layout.ConversionPattern=[%d] %-5p %l - %m%n



log4j.appender.ZERG=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ZERG.File=C:/logs/zerg.log
log4j.appender.ZERG.DatePattern='.'yyyy-MM-dd
log4j.appender.ZERG.Threshold=DEBUG
log4j.appender.ZERG.layout=org.apache.log4j.PatternLayout
log4j.appender.ZERG.layout.DateFormat=ISO8601
log4j.appender.ZERG.layout.TimeZoneID=GMT-8:00
log4j.appender.ZERG.layout.ConversionPattern=[%d] %-5p %l - %m%n


#################################
#  참고 log4j.properties 파일
#################################

log4j.rootCategory=, dest2, C

log4j.appender.C=org.apache.log4j.DailyRollingFileAppender
log4j.appender.C.File= error.log
log4j.appender.C.layout=org.apache.log4j.PatternLayout
#log4j.appender.C.layout.ConversionPattern=[%d{yyyy-MM-dd hh:mm:ss}] %-5p [%-10t] %c{2} - %m%n
log4j.appender.C.layout.ConversionPattern=[%d{yyyy-MM-dd hh:mm:ss}] - %m%n
log4j.appender.C.Append=true
log4j.appender.C.DatePattern='.'yyyyMMdd
log4j.appender.C.Threshold=ERROR
log4j.appender.C.ImmediateFlush=true


log4j.appender.dest2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dest2.File=debug.log
log4j.appender.dest2.layout=org.apache.log4j.PatternLayout
#log4j.appender.dest2.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %-5p [%-10t] %c{2} - %m%n
log4j.appender.dest2.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] - %m%n
log4j.appender.dest2.Append=true
log4j.appender.dest2.DatePattern='.'yyyyMMdd
log4j.appender.dest2.Threshold=DEBUG
log4j.appender.dest2.ImmediateFlush=true



#---------------------------------------------------------------------
# %n - newline                                                               
# %m - your log message                                               
# %p - message priority (FATAL, ERROR, WARN, INFO, DEBUG or custom)          
# %r - millisecs since program started running                               
# %% - percent sign in output                                                
#                                                                            
#-----------------------SOME MORE CLUTTER IN YOUR LOG-------------------------
# %c - name of your category (logger), %c{2} will outputs last two components
# %t - name of current thread                                                
# %x - Nested Diagnostic Context (NDC) (you supply it)        
#                                                                            
#-------------------------SLOW PERFORMANCE FORMATS----------------------------
# %d - date and time, also %d{ISO8601}, %d{DATE}, %d{ABSOLUTE},              
#        %d{HH:mm:ss,SSS}, %d{dd MMM yyyy HH:mm:ss,SSS} and so on       
# %l - Shortcut for %F%L%C%M                                                 
# %F - Java source file name                                                 
# %L - Java source line number                                             
# %C - Java class name, %C{1} will output the last one component             
# %M - Java method name    
#-----------------------------------------------------------------------




#################################
#
#################################

[펌] Log4J 사용하기

프로그래밍/Library 2007. 11. 28. 09:36 Posted by galad
1. 서언
이 문서는 GPL을 따릅니다. 편의상 경어는 생략합니다. 이 문서에서 다루고 있는 범위는 Log4J의 설치 및 환경설정, 그리고 간단한 예제 프로그램의 구동까지입니다. 추가의 내용을 원하시는 분은 Log4J의 홈페이지에 가셔서 보면 Log4J에 대한 책을 소개하고 있으니 참고하시기 바랍니다.

2. Context
Log4J는 Jakarta 프로젝트의 Sub Project로 자바에서 손쉽게 로그를 만들 수 있도록 해 준다. 일반적으로 디버깅을 위해서는 System.out.println을 사용하게 된다. 아니면 자체적으로 로그 기능을 담당하는 간단한 로그 클래스를 작성하기도 한다. 하지만 오픈소스로 만들어진 다양한 기능을 제공하고 퍼포먼스도 빠른 로그 시스템인 Log4J를 사용한다면 개발자들은 로그 보다는 좀 더 비즈니스 로직에 충실할 수 있게된다.
이에 Log4J를 설치하고 사용해 보고 그 설정 및 샘플을 남겨 보았다.

Log4J에 대한 소개는 http://jakarta.apache-korea.org/log4j/index.html에 있으니 자세한 내용을 보길 원한다면 위 페이지를 참고한다. 위 페이지는 한글로 번역된 페이지라 읽기에 부담없으리라 생각한다.

3. Install & Configuration
먼저 Log4J를 다운 받는다. Log4J는 http://jakarta.apache.org/log4j/docs/download.html에서 최신 버전을 다운로드 받을 수 있다. 이 글을 쓰는 시점에서 최신 버전은 1.2.7이다.
파일을 다운로드 받은 다음에 적절한 디렉토리에 압축을 푼다. 여기서는 압축을 푼 디렉토리를 $LOG4J_HOME이라 하겠다. $LOG4J_HOME에 보면 INSTALL 이라는 파일이 존재한다. 반드시 이 파일을 먼저 읽어 보길 권한다.
이 파일에는 Log4J를 어떻게 설치하고 테스트하는지 잘 설명이 되어 있다. 또한 이 글에서는 언급하고 있지 않는 Dependency부분들을 설명하고 있으니 JDK버전이 다른 사람들은 반드시 읽고 Dependency를 체크해 보길 바란다.
$LOG4J_HOMEdistlib에 보면 log4j-1.2.7.jar 파일이 존재한다. 이 파일을 CLASSPATH에 추가를 한다.

자바로 개발을 하다 보면 이것저것 툴도 설치하는 것도 많고 여러 Jar파일들도 CLASSPATH에 추가해야 하는 경우가 많다. 이러한 모든 Jar파일들을 시스템 환경변수나 사용자 환경변수의 CLASSPATH에 추가하게 되면 나중에 CLASSPATH때문에 고생하는 경우가 발생된다. 따라서 이런 방법보다는 cmd파일 또는 sh파일에 환경 변수를 셋팅하는 Script를 만들어 두고 사용하는 것을 추천한다.

이제 클래스패스에 log4j-1.2.7.jar를 추가했으면 다음의 프로그램을 통해서 제대로 셋팅이 되었는지 체크해
보도록 하자.
############################## Test Program ##############################
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class Hello {

  static Logger logger = Logger.getLogger(Hello.class);

  public
  static
  void main(String argv[]) {
  BasicConfigurator.configure();
  logger.debug("Hello world.");
  logger.info("What a beatiful day.");
  }
}
##############################  End of Program ##############################
위의 Hello.java를 컴파일을 하고 실행을 시켜본다.
만일 Exception in thread "main" java.lang.NoClassDefFoundError: Hello 이라는 메시지가 나온다면 클래스 패스에 현재 디렉토리(.)를 추가한다. 실행을 시켰을때,

10 [main] DEBUG Hello  - Hello world.
20 [main] INFO Hello  - What a beatiful day.

이런 Output이 나온다면 제대로 셋팅이 된 것이다.

4. Sample Program
이제 Log4J를 이용한 간단한 로그를 남기는 예제를 만들어보자. 이 예제는 3가지 종류의 로그를 남기는 아주 간단한 프로그램이다.
첫번째 로그는 파일로 로그를 남기면서 그 파일의 크기가 10KB 이상이면 백업으로 보관하고
새로운 파일에 로그를 작성하게 된다.
두번째 로그는 파일로 로그를 남기지만 날짜별로 백업을 남기게 된다.
세번째는 Project 전체에 대한 로그를 파일로 남기게 된다.

먼저 프로그램 코드를 보도록 하자.

############################## Sample Program ############################## import org.apache.log4j.*;

public class TestLogging {
  static Logger esoLogger = Logger.getLogger("posdata.log.esourcing");
  static Logger bidLogger = Logger.getLogger("posdata.log.ebidding");
  public static void main(String[] args) {

    esoLogger.debug("ESO Debug");
    bidLogger.debug("BID Debug");
    esoLogger.info("ESO INFO");
    bidLogger.info("BID info");
    esoLogger.warn("ESO WARN");
    bidLogger.warn("BID WARN");
    esoLogger.error("ESO ERROR");
    bidLogger.error("BID ERROR");
    init();
    property();
  }

  public static void init() {
    Logger hr = Logger.getLogger("posdata.log.hr");
    hr.debug("HR debug");
    hr.info("HR Info");
    hr.warn("HR Warn");
    hr.error("HR Error");
  }

  private static void property() {
    java.util.Properties props = System.getProperties();
    java.util.Enumeration enum = props.propertyNames();
    String key = null;
    while(enum.hasMoreElements()) {
      key = (String)enum.nextElement();
      bidLogger.info(key + " : " + props.getProperty(key));
    }
  }
}
############################## End of Program ##############################

다음은 위 프로그램의 로그설정을 가지고 있는 log4j.properties 파일이다. '#'표시는 주석문이 된다. ESO_Appender의 설정은 로그 파일의 크기가 10KB이상이 되면 백업으로 남기게 하는 설정이고, BIS_Appender의 설정은 날짜별로 백업을 남기게 되는 설정이다. 만일 여기에 설정된 Appener이외에 다른 것을 Logger를 사용 했다면 ROOT_Appender를 통해서 로그를 남기게 된다. 이 Properties 파일은 반드시 Classpath에 포함되어 있어야 한다.

############################## log4j.properties ##############################
log4j.rootCategory=DEBUG, ROOT_Appender
log4j.category.posdata.log.esourcing=INFO, ESO_Appender
log4j.category.posdata.log.ebidding=INFO, BID_Appender
log4j.additivity.posdata.log.ebidding=false
log4j.additivity.posdata.log.esourcing=false

# Set ESO_Appender specific options.
# log4j.appender.ESO_Appender=org.apache.log4j.ConsoleAppender
log4j.appender.ESO_Appender=org.apache.log4j.RollingFileAppender
log4j.appender.ESO_Appender.File=./eso.log
log4j.appender.ESO_Appender.Append=true
log4j.appender.ESO_Appender.MaxFileSize=10KB
log4j.appender.ESO_Appender.MaxBackupIndex=12
log4j.appender.ESO_Appender.layout=org.apache.log4j.PatternLayout
log4j.appender.ESO_Appender.layout.ConversionPattern=%d{<yyyy-MM-dd HH:mm:ss>} <%-5p> <%l>: %m%n

# Set BID_Appender specific options.
log4j.appender.BID_Appender=org.apache.log4j.DailyRollingFileAppender
log4j.appender.BID_Appender.ImmediateFlush=true
log4j.appender.BID_Appender.File=./bid.log
log4j.appender.BID_Appender.Append=true
log4j.appender.BID_Appneder.DatePattern='.'yyyy-MM-dd
# log4j.appender.BID_Appender.MaxFileSize=10KB
# log4j.appender.BID_Appender.MaxBackupIndex=9
log4j.appender.BID_Appender.layout=org.apache.log4j.PatternLayout
log4j.appender.BID_Appender.layout.ConversionPattern=%d{<yyyy-MM-dd HH:mm:ss>} <%-5p> <%-l>: %m%n
# log4j.appender.BID_Appender.layout.ConversionPattern=%d{<yyyy-MM-dd HH:mm:ss>} %-15.15t %-8.8p %-30.30c %x: %m%n

# Set ROOT_Appender specific options.
log4j.appender.ROOT_Appender=org.apache.log4j.RollingFileAppender
log4j.appender.ROOT_Appender.File=./project.log
log4j.appender.ROOT_Appender.Append=true
log4j.appender.ROOT_Appender.MaxFileSize=10kb
log4j.appender.ROOT_Appender.MaxBackupIndex=9
log4j.appender.ROOT_Appender.layout=org.apache.log4j.PatternLayout
log4j.appender.ROOT_Appender.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-15.15t %-8.8p %-30.30c %x %C %F %l %L %M: %m%n
############################ End of Properties File ############################

응용프로그램을 개발하다 보면 많은 System.out.println()을 남발하게 된다. 개발된 프로그램을 테스트하고 가동에 들어가면 System.out.println()문을 지우던가 아니면 지저분하게 두어야 하는 문제가 발생한다. 하지만 위와 같이 Log4J를 사용하면 개발과 테스트시에는 DEBUG 레벨로 두고 운영시에는 좀 더 레벨을 올려서 설정할 수 있게 되기에 좀 더 시스템 개발과 운영에 있어서 깔끔하게 된다. 또한 레벨별로 로그를 남길 수 있고 운영 중에도 동적으로 설정을 바꿀 수 있고, 로그를 남기는 형식 또한 다이나믹하게 지원하기 때문에 그 유용성이 크다고 볼 수 있다.

마지막으로 위에서 사용된 각 Pattern들의 인자값은
http://www.vipan.com/htdocs/log4jhelp.html에서 찾아볼 수 있다. 참고하길 바란다(단 해당문서가 Log4J Version 1.1을 기준으로 한거 같으니 잘 구별해서 사용하길 바란다).
 
Tomcat의 Logger(로거)가 이상해요..
Tomcat의 로거가 다음과 같은 오류를 내며 작동하지 않는다면...

log4j:WARN No appenders could be found for logger (org.apache.commons.digester.Digester).
log4j:WARN Please initialize the log4j system properly.


혹은 내가 작성한 Log4j 를 사용하는 웹 어플리케이션의 로거가 내가 설정하지 않는 프라퍼티를 읽으려 들거나 한다면, 그것의 거의 log4j-*.jar가 의도하지 않게 클래스패스로 잡혀 있기 때문이다.

웹 어플리케이션의 log4j-*.jar 는 "WEB-INF/lib" 디렉토리 아래에만 둔다. 결코 "$CATALINA_HOME/common/lib" 등에 두지 말아야 한다.

또한 log4j-*.jar를 클래스 패스로 지정하지도 말고, $JAVA_HOME/jre/lib/ext 디렉토리에 둬서도 안된다.

이거 때문에 며칠 삽질... --;

절대 명심해야 할 사항중에 하나는 스스로 절대적인 확신이 없으면 $JAVA_HOME/jre/lib/ext 디렉토리에는 절대로 다른 *.jar 파일을 넣어 두면 안된다!!! 항상 수동으로 클래스패스를 설정하도록 하고, TOMCAT의 경우 WEB-INF/lib를 자기 스스로 클래스패스를 잡아주니, 그곳을 이용하도록 한다.
 
http://kwon37xi.egloos.com/2176400

Eclipse WTP에서 Apache Cactus사용하기

Cactus는 Web Application 에 올라가 있는 servlet, jsp, filter등을 테스트 할수 있도록 하는 JUnit 기반위의 Framework이다. (HttpUnit 과의 관계가 어떻게 되는지 모르겠네요..)

Cactus를 이용하여 서블릿에 대한 request를 설정하고 response를 확인할 수 있다.


  • New -> Java -> JUnit -> Servlet Test Case 를 클릭하여 Servlet Test Case를 만들면 자동으로 Cactus 설정이 프로젝트에 추가된다.
  • classpath 경로 안에 cactus.properties 파일을 만들고 아래와 같이 연동할 web application 의 url을 잡아준다.

  • 아래를 참고해서 Servlet Test Case를 만든다. beginXXX 에서 request를 보내기 전 작업을 할 수 있고, endXXX에서 response 에 대한 데이터를 확인할 수 있다.

public class ServletTest extends ServletTestCase {

    public void testProdSpecSel() throws IOException {

        ProdSpecSel servlet = new ProdSpecSel();

        servlet.service(request, response);

    }

    public void beginProdSpecSel(WebRequest request) throws Exception{

        String data = "product_spec";

        InputStream input = new ByteArrayInputStream(data.getBytes());

        request.setUserData(input);

    }

    public void endProdSpecSel(WebResponse response) throws Exception{

        System.out.println(response.getText());

    }

}

  • Web Application 을 띄운후 JUnit으로 실행하면 Test가 수행된다.

http://skccdev.pbwiki.com/Cactus

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

출처 블로그 > 쁘레오아저씨네
원본 http://blog.naver.com/freo39/110004398601

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() {
      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/

문서에 대하여

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

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


[펌] http://wiki.javajigi.net/display/OSS/TDD#TDD-JUnit4.0%EC%97%90%EC%84%9C%EC%9D%98%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD


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자동생성




 

[펌] JUnit 2

프로그래밍/Library 2007. 11. 28. 09:33 Posted by galad

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

출처 블로그 > もも***
원본 http://blog.naver.com/dmsl01/80035693427

JUnit 


 

테스트 클래스 작성
JUnit Test 클래스는 junit.framework.TestCase 를 상속받고,

public void test*() 형태의 테스트 메소드를 작성한다.

 

JUnit 테스트의 골격
1. junit.framework.* 을 코드에 반영하기 위한 import 문
2. 테스트 클래스가 TestCase를 상속하는 extends 문(되도록 TestCase를 상속하는 다른 클래스를 만들고, 그 클래스로 다시 상속 받아 테스트 클래스를 만드는 것이 좋다)
3. super(string)을 호출해 주는 생성자


테스트 수행
 * 텍스트 : junit.textui.TestRunner TestClass
 * AWT : junit.awtui.TestRunner TestClass
 * SWING : junit.swingui.TestRunner TestClass

 

테스트 수행 메소드 부르기
public static void main(String[] args) {
    junit.swingui.TestRunner.run(AllTest.class);
}


Assertions
각 메소드는 맨 처음 인자로 문자열 설명을 가질 수 있다.

assertEquals(primitive expected, primitive actual);
assertEquals(Object exprected, Object actual);
assertSame(Object exptected, Object actual); // 두 객체가 같은가?
assertNotSame(Object exptected, Object actual);
assertNull(Object object); // 객체가 NULL인가?
assertNotNull(Object object);
assertTrue(boolean condition);
assertFalse(boolean condition);

fail(String str); : 무조건 실패


테스트 클래스의 값 초기화
테스트 클래스에 public void setUp() 메소드를 만들어주면 각 테스트 메소드가 실행되기 전에 먼저 setUp() 메소드가 실행된다.

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

suite()
테스트할 메소드를 명시적으로 지정하고 싶을 때 테스트 클래스에 public static Test suite() 메소드를 만든다.
public static Test suite() {
    TestSuite = new TestSuite();
    suite.addTest(new TestClass("testMethod1")); // 테스트할 메소드 이름
    suite.addTest(new TestClass("testMethod2"));

    return suite;
}


JUnit과 ANT

<junit printsummary="true" haltonfailure="no">
        <classpath>
                <pathelement path="${classes.dir}"/>
        </classpath>
        <test name="helloproject.junit1.DayCounterTest"/>
</junit>

 

haltonfailover="yes" 이면 테스트가 하나라도 실패하면 거기서 테스트를
중지해버린다.

여러 테스트를 수행하려면

<junit printsummary="true" haltonfailure="no">
        <classpath>
                <pathelement path="${classes.dir}"/>
        </classpath>
        <formatter type="xml"> <!-- 결과를 XML로 내보냄 -->
        <batchtest todir="${test.dir}"> <!-- XML결과가 저장될 Directory -->
                <fileset dir="${classes.dir}">
                        <include name="**/*Test*.class"/>
                </fileset>
        </batchset>
</junit>

 

테스트 결과 XML을 이용해 HTML로 결과 보고서 만들기 - Xalan 2 필요

<junitreport todir="${test.dir}">
        <fileset dir="${test.dir}">
                <include name="TEST**.xml"/>
        </fileset>
        <report format="frames" todir="${test.dir}/html"/>
</junitreport>