/////////////////////////// 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; } }
<!-- 사용자 정의 심플 타입 정의 --> <xsd:simpleType name="stMemberKind"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="유료"/> <xsd:enumeration value="무료"/> </xsd:restriction> </xsd:simpleType>
<!-- 엘리먼트 선언 --> <!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">
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)나 기호(`.` , `-`)등을 넣으면 그대로 출력됩니다.
- 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, 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)); }
//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;
이전에 쓴 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">
/** * <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 {
이것을 잘 따라했다면...아래와 같은 로그를 볼수 있다. 로그 파일 이름은 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 을 적절한 위치에 넣어두면 된다. 아마 다 될것이라고 믿는다.
/** * 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으로 실행한후 메소드 명만 수정하였으므로 실행은 그렇게 하면 실행이 가능하다. * ***************************************************************************** * */
// 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!!!");
// 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.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();
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"); }
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.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파일은 다음과 같을것이다.
다양한 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
! 매일 자정에 로그파일을 교체하며 기존파일은 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
#--------------------------------------------------------------------- # %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 #-----------------------------------------------------------------------
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 ##############################
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) {
다음은 위 프로그램의 로그설정을 가지고 있는 log4j.properties 파일이다. '#'표시는 주석문이 된다. ESO_Appender의 설정은 로그 파일의 크기가 10KB이상이 되면 백업으로 남기게 하는 설정이고, BIS_Appender의 설정은 날짜별로 백업을 남기게 되는 설정이다. 만일 여기에 설정된 Appener이외에 다른 것을 Logger를 사용 했다면 ROOT_Appender를 통해서 로그를 남기게 된다. 이 Properties 파일은 반드시 Classpath에 포함되어 있어야 한다.
############################ End of Properties File ############################
응용프로그램을 개발하다 보면 많은 System.out.println()을 남발하게 된다. 개발된 프로그램을 테스트하고 가동에 들어가면 System.out.println()문을 지우던가 아니면 지저분하게 두어야 하는 문제가 발생한다. 하지만 위와 같이 Log4J를 사용하면 개발과 테스트시에는 DEBUG 레벨로 두고 운영시에는 좀 더 레벨을 올려서 설정할 수 있게 되기에 좀 더 시스템 개발과 운영에 있어서 깔끔하게 된다. 또한 레벨별로 로그를 남길 수 있고 운영 중에도 동적으로 설정을 바꿀 수 있고, 로그를 남기는 형식 또한 다이나믹하게 지원하기 때문에 그 유용성이 크다고 볼 수 있다.
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를 자기 스스로 클래스패스를 잡아주니, 그곳을 이용하도록 한다.
개발 후기 단계에서 발견된 결함들을 수정하기에는 비용과 시간이 많이 소비되고, 프로젝트가 실패될 확률이 높아진다.
개발 단계에서의 효율적인 테스팅은 전체 프로젝트 시간을 감소시킨다.
테스트의 종류
단위테스트 : JUnit 단위테스트는 단위 코드에서 문제 발생 소지가 있는 모든 부분을 테스트 하는 작업이다. 보통 클래스의 public method 를 테스트 한다. 좋은 단위 테스트란 모든 메서드를 테스트 하는 것이 아니라, 상식 수준의 확인을 통해 단위 코드가 의도한 대로 동작하는지 여부를 판단하는 단계이다. 이상적으로는 코딩전에 테스트 케이스를 작성하여 구현시 보조자료로 활용하는 것이 좋다. ( TDD의 기법 ) 단위테스트 후에 개발팀은 테스트를 프로젝트 테스트 스위트에 추가하여 매일 여러번 수행하고 모든 테스트를 항상 통과하게 해야 한다. 기회가 된다면 Code Coverage 를 하는 것이 좋은데 오픈소스로는 JCoverage 등이 있다.
통합/컨테이너 내부 테스트 : Cactus 좋은 단위 테스트는 시스템내의 복잡한 부분에 관계없이 클래스 내의 함수들을 검사하는 것이다. 단위 테스트는 가능한 의존성 없이 독립적으로 처리되어야 한다. Mock Object 로 테스트를 하는 경우도 있지만, Cactus 는 J2EE 컨테이너에 접근하는 방법을 제공한다. 컨테이너 안에서 코드 테스트가 가능하도록 하기 위해서 Cactus 는 상세하거나 또는 까다로운 실제와 같은 모형을(mock-ups) 개발자에게 제공한다. 이 방법은 실행되는 코드가 제품이 출시되는 환경에서 실행되기 때문에 또 다른 피드백 기준을 제공한다. 컨테이너 서비스와 상호 작용하는 단일 오브젝트 경우에 컨테이너 내부 테스트를 사용하여 간편한 단위 테스트를 할 수 있다.
수락/기능 테스트 : HttpUnit 기능 테스트는 전체 시스템이 의도한 바대로 동작하는 지를 검사하는 과정이다. 이 방법은 완성된 시스템을 고객으로부터 검사받는 방법이므로 수락 테스트라고도 한다. 기능 테스트는 구조적 기능에 대하여 어떤 프로그램의 기능에 대한 시험이며 진척 상태를 확인하고 이전의 테스트나 누락된 결점을 잡아내거나 미완성 또는 불완전한 부분에서 발생된 문제를 찾아내는 것이 중요하다. 수락 테스트는 고객에 의해 작성된다. 기능 테스트는 항상 100% 구현될 필요는 없으나 제품 출시 전에는 100% 수행 되어야 할것이다. 기능 테스는 종종 매우 구체적인 내용들을 테스트 하기도 한다. 아직은 통괄적인 수락 테스팅 툴은 나오지 않았고 Junit은 어떤 자바클래스에서도 수행될 수 있으나 수락 테스팅 도구는 특정 애플리케이션 요구에 따라 작성되어야 한다. HttpUnit을 이용하면 테스팅 API를 이용하여 웹 리소스에 대한 호출과 응답 값 조회를 프로그래밍 할 수 있도록 한다.
자동화된 테스트로 개발을 이끌어 가는 개발 방식을 테스트 주도 개발이라 부른다. TDD는 분석 기술이며, 설계 기술이며, 개발의 모든 활동을 구조화하는 기술이다. 작동하는 깔끔한 코드(clean code that works). 이 핵심을 찌르는 한마디가 바로 테스트 주도 개발의 궁극적인 목표다.
테스트 주도 개발에서는 두 가지 단순한 규칙만을 따른다.
오직 자동화된 테스트가 실패할 경우에만 새로운 코드를 작성한다.
중복을 제거한다.
또한 위의 두 가지 규칙에 의해 프로그래밍 순서가 다음과 같이 결정 된다.
빨강- 실패하는 작은 테스트를 작성한다. 처음에는 컴파일조차 되지 않을 수 있다.
초록- 빨리 테스트가 통과하게끔 만든다. 이를 위해 어떤 죄악을 저질러도 좋다. (죄악이란 기존 코드 복사해서 붙이기-copy and paste, 테스트만 간신히 통과할 수 있게끔 함수가 무조건 특정상수를 반환하도록 구현하기 등을 의미한다.)
1. TestCase 클래스 : 가장 간단하게 Junit을 사용하는 방법은 TestCase 클래스를 상속받은 클래스를 작성하는 것이다. 이 클래스에는 test로 시작하는 메소드만 나열하면 된다.
2. TestSuite 클래스 : testCase 클래스를 상속받은 클래스만을 사용하다 보면, 일부의 test 메소드는 간혹 실행하지 않고 싶거나 특정한 test 메소드만 실행하고 싶을 때가 생긴다. (①) 또는 Test 클래스를 한데 묶어서 한꺼번에 실행하고 싶은 경우(②)도 발생한다. 이때 사용하는 것이 바로 TestSuite이다.
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)를 사용할 수 있다.
좋은 단위 테스트는 시스템내의 복잡한 부분에 관계없이 클래스 내의 함수들을 검사하는 것이다.
단위 테스트는 가능한 의존성 없이 독립적으로 처리되어야 한다.
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로 시작된 메서드들을 찾아서 리플렉션을 통해서 테스트를 수행하게 한다.
테스트 코드는 이런 작업들을 수행하도록 설정한다.
테스트에 필요한 모든 조건과 상황을 준비 설정한다. 테스트 대상이 되는 메서드를 호출한다. 테스트 대상이 되는 메서드가 원하는 대로 동작한다는 것을 검증한다. 실행이 끝나고 다른 코드에 영향이 없게 정리작업을 한다.
JUnit 이란 독립된 테스트를 할 수 있도록 도와주는 framework이다.
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프레임워크
import문으로 junit클래스들을 참조
테스트를 포함한 클래스는 모두 TestCase를 상속해야 한다.
테스트 클래스는 각 test.... 메서드를 정의한다.
모든 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() 메서드를 이용하여 해결하면 될것이다.
public void testEmpAccess() { //dbConn 사용 어쩌고 저쩌고.... }
public void testDeptAccess() { //dbConn 사용 어쩌고 저쩌고.... } }
이 간단한 예제에서는 setUp이 호출된 다음에 testEmpAccess() 가 호출이 되고 tearDown() 이 호출된다.
그리고 setUp() 이 다시 호출되고 testEmpAccess() 가 실행되고 난 후에 tearDown()이 다시 호출된다.
대개는 이렇게 테스트별 준비 설정만으로 충분하지만 어떤 상황에서는 "전체" 테스트 스위트를 실행하기위해 어떤것을 설정하거나 정리해야 할 필요가 있다. 이런 경우 스위트별 준비 설정과 정리가 필요한데 이 설정은 조금 더 복잡하다. 필요한 테스트들의 스위트를 반들어서 TestSetup 객체 안에 감싸 넣어야 한다.
publicstatic Test suite() { TestSuite suite = new TestSuite();
// Only include short tests suite.addTest(new TestClassTwo("testShortTest")); suite.addTest(new TestClassTwo("testAnotherShortTest"));
JUnit에서 제공하는 asssert 메서드만 사용하는 것보다 직접 사용자 정의 클래스를 만들어 상속하여 사용하는 것이 좋다.
JUnit과 예외
테스트에서 발생하는 예상된 예외
뭔가 크게 잘못 되어서 발생하는, 예상하지 못한 예외 우리가 일반적으로 생각하는 것과는 달리 예외는 무언가 잘못되었다는 것을 알려주는 굉장히 유용한 도구다. 가끔 테스트에서는 테스트 대상이 되는 메서드가 예외를 발생시키기를 '바라는' 경우도 있다. 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를 적용하여 검증해 볼 수 있다.
다른 수단을 이용한 교차확인
에러조건을 강제로 만들어내기
성능특성
모의객체 사용하기 - 단위 테스트의 목표는 한번에 메서드 하나를 테스트 하는 것이다. 하지만, 테스트 하기가 어려운 상황이라면?
하지만 우리에게 필요한것은 똑같은 일을 해내면서 더 깔끔하고, 더 객체지향 적인 방법이다.
모의 객체
진짜 객체가 비결정적인 동작을 한다.( 예상할 수 없는 결과를 만들어 낸다.) 진짜 객체를 준비 설정하기 어렵다. 진짜 객체가 직접 유발시키기 어려운 동작을 한다. 진짜 객체가 느리다. 진짜 객체가 사용자 인터페이스를 가지거나, 사용자 인터페이스 자체다. 테스트가 진짜 객체에게 그것이 어떻게 사용되었는지 물어보아야 한다. 진짜 객체가 아직 존재하지 않는다.( 다른팀이나 새로운 하드웨어 시스템과 함께 일할 때 흔한 문제다.)
모의 객체를 사용하기 위한 세단계 *객체를 설명하기 위해 인터페이스를 사용한다. *제품 코드에 맞게 그 인터페이스를 구현한다. *테스트에 쓸 모의 객체의 인터페이스를 구현한다.
모의 객체의 사용법의 핵심은 각자가 흉내내는 뭔가에 의존하는 객체를 테스트 하기 위한것이다.
좋은 테스트의 특징
자동적 : 테스트를 실행하는 경우와 결과를 확인하는 경우를 모두 의미하는 것.
철저함 - 문제가 될 수 있는 것은 모두 테스트한다.
반복가능
독립적
다른 테스트로부터도 독립적, 환경, 다른 개발자 로부터도 독립적
전문적 : 전문적 표준을 유지하면서 작성되어야 한다.
테스트를 테스트 하기위한 방법의 일환으로 일부러 버그를 집어넣어 테스트를 검증하기도 한다. 그리고 코드를 고쳐서 테스트를 통과하게 하면 된다.
프로젝트에서 코드의 구조화의 방법
같은 디렉터리
가장 쉬운 방법
단점은 제품 코드와 테스트 코드가 같이 섞여 어질러져 있다는 것
하위 디렉토리
이방법은 테스트 코드를 적어도 제품 코드와 똑같은 디렉터리에 있지는 않게 떨어뜨려 놓을 수 있다는 이점이 있다.
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 한다.)
테스트 클래스 작성 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); }
테스트 클래스의 값 초기화 테스트 클래스에 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"));