맵리듀스에서 리듀스 출력결과를 RDBMS로 저장 시 GC overhead limit exceeded Issue가 발생한다.

해당 Issue는 리듀스 출력결과가 즉 RDBMS로 저장되어야 하는 데이터가 많은 경우 발생하게 된다.


맵리듀스에서 RDBMS로 Insert하기 위해 org.apache.hadoop.mapreduce.lib.db.DBOutputFormat(링크)를 

사용한다. 해당 소스를 까보면 리듀스 출력결과로 Insert문자열을 생성하여 addBatch()로 보관 후 DB Connection이 close될때 executeBatch()가 실행되고 commit()이 실행된다. 즉 대량의 데이터가 한번에 Insert되는 것이다.

해당 Issue와 관련해서 Cloudera블로그에도 관련 글이 존재한다. (링크)


이 문제를 해결하기 위해 DBOutputFormat을 상속받아 삽질을 했으나 해결방법은 의외로 간단하다.


    public static final int batchSize = 5000;
    public static int count = 0;

    public static class DBOutputWritable implements Writable, DBWritable {
        String col1, col2;

        public DBOutputWritable(String col1, String col2) {
            this.col1 = col1;
            this.col2 = col2;
        }

        public void readFields(DataInput in) throws IOException {}

        public void readFields(ResultSet rs) throws SQLException {
            col1 = rs.getString(1);
            col2 = rs.getString(2);
        }

        public void write(DataOutput out) throws IOException {}

        public void write(PreparedStatement ps) throws SQLException {
            ps.setString(1,col1);
            ps.setString(2,col1);
            
            if(++count % batchSize == 0) {
                ps.executeBatch();
                count=0;
            }
           
        }
    }
DBOutputWritable에서 write메소드에서 데이터 단위를 나누어 주면된다. 

끝.


저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글이 없습니다.
secret

AWS JAVA SDK를 이용하여 개발한 MapReduce를 EMR Custom Jar로 등록하여 Job Flow를 생성 및 실행하는 방법이다.

private static final String EMR_SETUP_NAME = "Setup hadoop debugging";
private static final String HADOOP_SETUP_JAR  = "s3://elasticmapreduce/libs/script-runner/script-runner.jar";
private static final List<String> HADOOP_SETUP_ARGS_AS_LIST = Arrays.asList("s3://elasticmapreduce/libs/state-pusher/0.1/fetch");
private static final String HADOOP_VERSION = "하둡버전";
private static final int INSTANCE_COUNT = 4;   //master 1개, slave 3개
private static final String INSTANCE_TYPE = InstanceType.C3Xlarge.toString();
private static final String FLOW_NAME = "FLOW_NAME";
private static final String EMR_NAME = "EMR_NAME";
private static final String S3N_HADOOP_JAR = "s3n://버킷이름/폴더명/커스텀.jar";
private static final String S3N_LOG_URI  = "s3://버킷이름/Log/";
private static final String[] JOB_ARGS = new String[] { "" };
private static final List<String> ARGS_AS_LIST = Arrays.asList(JOB_ARGS);
private static final List<JobFlowExecutionState> DONE_STATES = Arrays.asList(new JobFlowExecutionState[] { JobFlowExecutionState.COMPLETED,
                                             JobFlowExecutionState.FAILED,
                                             JobFlowExecutionState.TERMINATED });

static AmazonElasticMapReduce emr;
private static void init() throws Exception {
    AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
    emr = new AmazonElasticMapReduceClient(credentials);
}

public static void main(String[] args) throws Exception {
    init();
    try {
        JobFlowInstancesConfig instances = new JobFlowInstancesConfig();
        instances.setHadoopVersion(HADOOP_VERSION);
        instances.setInstanceCount(INSTANCE_COUNT);
        instances.setMasterInstanceType(INSTANCE_TYPE);
        instances.setSlaveInstanceType(INSTANCE_TYPE);
        /*각종 Job Flow 설정 추가 ex) instances.setTerminationProtected(true); */

        RunJobFlowRequest request = new RunJobFlowRequest(FLOW_NAME, instances);
        request.setLogUri(S3N_LOG_URI);

        //step 추가로 초기 서버에 하둡을 세팅하기 위한 step이다.            
        HadoopJarStepConfig jarConfig_setting = new HadoopJarStepConfig(HADOOP_SETUP_JAR);
        jarConfig_setting.setArgs(HADOOP_SETUP_ARGS_AS_LIST);
        StepConfig stepConfig_setting = new StepConfig(EMR_SETUP_NAME, jarConfig_setting);
        //custom jar step추가
        HadoopJarStepConfig jarConfig = new HadoopJarStepConfig(S3N_HADOOP_JAR);
        jarConfig.setArgs(ARGS_AS_LIST);
        StepConfig stepConfig = new StepConfig(EMR_NAME, jarConfig);

        request.setSteps(Arrays.asList(new StepConfig[] {stepConfig_setting, stepConfig}));

        RunJobFlowResult result = emr.runJobFlow(request);

        // 생성한 EMR에 대한 실행 상태코드 체크
        String lastState = "";
        STATUS_LOOP: while (true) {
            DescribeJobFlowsRequest desc = new DescribeJobFlowsRequest(Arrays.asList(new String[] {result.getJobFlowId()}));
            DescribeJobFlowsResult descResult = emr.describeJobFlows(desc);
            for (JobFlowDetail detail : descResult.getJobFlows()) {
                if (isDone(detail.getExecutionStatusDetail().getState())) {
                    System.out.println(detail.getExecutionStatusDetail().getState());
                    break STATUS_LOOP;
                } else if (!lastState.equals(detail.getExecutionStatusDetail().getState())) {
                    lastState = detail.getExecutionStatusDetail().getState();
                    System.out.println(lastState);
                }
            }
          Thread.sleep(10000);
        }
    } catch (AmazonServiceException ase) {
        System.out.println("Caughtption: "      + ase.getMessage());
        System.out.println("Reponseus Code: "   + ase.getStatusCode());
        System.out.println("Error: "            + ase.getErrorCode());
        System.out.println("Request"            + ase.getRequestId());
    }
}

public static boolean isDone(String value)
{
    JobFlowExecutionState state = JobFlowExecutionState.fromValue(value);
    return DONE_STATES.contains(state);
}

끝.


참고 : http://mpouttuclarke.wordpress.com/2011/06/24/how-to-run-an-elastic-mapreduce-job-using-the-java-sdk/

저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글이 없습니다.
secret

S3 특정 bucket에 저장된 Object 파일리스트 목록을 가져올때 아래와 같이 코딩을 할 수 있다.

AWSCredentials crd = new BasicAWSCredentials(accessKey, secretKey);
AmazonS3 s3 = new AmazonS3Client(crd);
ObjectListing objects = s3.listObjects(bucketName, folderName);
do {
        //1000개 단위로 읽음
	for (S3ObjectSummary objectSummary : objects.getObjectSummaries()) {
      
	}
      objects = s3.listNextBatchOfObjects(objects);  <--이녀석은 1000개 단위로만 가져옴..
} while (objects.isTruncated());

지정된 bucket에 S3 Object가 10,300개가 있다고 가정하면 위와 같이 코딩을 하게 되면

1000개 단위 10번 즉 10,000개의 Object만 가져올 수 있다.


전체 데이터를 다 가져오려면 아래와 같이 코딩을 해야 한다.

AWSCredentials crd = new BasicAWSCredentials(accessKey, secretKey);
AmazonS3 s3 = new AmazonS3Client(crd);
ListObjectsRequest listObject = new ListObjectsRequest();
listObject.setBucketName(bucketName);
listObject.setPrefix(folderName);
            
ObjectListing objects = s3.listObjects(listObject);
do {
	objects = s3.listObjects(listObject);
        //1000개 단위로 읽음
	for (S3ObjectSummary objectSummary : objects.getObjectSummaries()) {
      
	}
      //objects = s3.listNextBatchOfObjects(objects);  <--이녀석은 1000개 단위로만 가져옴..
      listObject.setMarker(objects.getNextMarker());
} while (objects.isTruncated());
끝.


저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글 하나 달렸습니다.
secret

이번에 정리하고자하는 내용은 챕터 2의 4항목인 "private 생성자를 사용해서 인스턴스 생성을 못하게 하자"이다.

블로그 작성 목적은 복습과 이 책을 다 읽고자하는 다짐으로 시작한다.

 

1. 문제점

  프로젝트 진행 시 산술연산이나 배열작업 등의 유틸리티 클래스 류를 많이 만들어 사용한다.

  해당 클래스들은 static 메소드와 static 필드로 흔히 구성되어져 있으며 이 클래스들은 인스턴스를 생성하지 

  못하게 설계되어 있다. 인스턴스 생성이 무의미 하기 때문이다. 

  하지만 컴파일러는 생성자가 없는 클래스라도 디폴트(default) 생성자를 만들어준다.

  여기서 문제점은 javadoc 프로그램으로 생성하는 API 문서에 해당 생성자가 나타나므로 인스턴스가 생성 가능한 

  클래스로 오인될 수 있다.


2. 해결방법

  인스턴스를 생성할 수 없게 private 생성자를 정의하면 인스턴스 생성이 불가능한 클래스로 만들 수 있으며 

  API 문서에도 나타나지 않는다.

public class UtilityClass {
    private UtilityClass() {
        throw new AssertionError();
    }
}

결론

이렇게 하면 생성자가 private이므로 외부에서 해당 클래스 인스턴스를 생성할 수 없으며 클래스 내부에서라도 

우연히 호출했을 경우 AssertionError 예외가 발생하게 된다.

위와 같이 하더라도 주석을 통해 해당 클래스의 인스턴스 불가 내용을 작성해두는게 좋다.

저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글이 없습니다.
secret

이번에 정리하고자하는 내용은 챕터 2의 3항목인 "private 생성자나 enum 타입을 사용해서 싱글톤의 특성을 유지하자"이다. 블로그 작성 목적은 복습과 이 책을 다 읽고자하는 다짐으로 시작한다.

 

1. 싱글톤

  디자인 패턴 중 가장 간단하면서도 자주 사용하는 패턴 중 하나이며 하나의 인스턴스만 생성되는 클래스이다.


2. 싱글톤 사용방법

  나는 이책에서 예로 설명하는 싱글톤 패턴 중 static 팩토리 메소드 패턴을 자주 사용하고 있으며 이책에서는 세가지의 

  싱글톤 패턴 방법을 소개하고 있다.

  가. public final 필드를 갖는 싱글톤  

public class Y2kpooh {
    public static final Y2kpooh INSTANCE = new Y2kpooh();
    private Y2kpooh() { }
}

private 생성자는 딱 한번만 호출되어 public static final 필드인 Y2kpooh.INSTANCE를 초기화 한다.

이 방법의 이점은 멤버 필드만 봐도 싱글톤 클래스로 알수 있는 장점은 있다.


  나. static 팩토리 메소드를 갖는 싱글톤

public class Y2kpooh {
    private static final Y2kpooh INSTANCE = new Y2kpooh();
    private Y2kpooh() { }
    public static Y2kpooh getInstance() { return INSTANCE; }
}

이 방법의 이점은 클래스에서 반환하는 싱글톤 인스턴스의 형태를 변경할 수 있는 유연성을 제공한다.


위 두 가지 방법으로 구현한 싱글톤 클래스를 직렬화 하기 위해서는 Serializable을 implements해야 하며 싱글톤을 보장받기 위해서는 인스턴스 필드를 transient로 선언해야 하며 readResolve 메소드를 추가해야 한다.


  다. 열거형(Enum)싱글톤

public enum Y2kpooh {
    INSTANCE;
    public void leaveTheBuilding() {}
}

이 방법은 가 항목과 기능적으로는 동일하지만 더 간단하며 앞서 나온 가,나 항목과 다르게 직렬화가 자동으로 지원되고 인스턴스가 여러 개 생기지 않도록 보장하는 장점이 있다.


결론

싱글톤 구현 시 열거형(Enum) 방법이 가장 좋은 방법이다.

저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글이 없습니다.
secret

이번에 정리하고자하는 내용은 챕터 2의 2항목인 "생성자의 매개변수가 많을 때는 빌더(builder)를 고려하자"이다.

블로그 작성 목적은 복습과 이 책을 다 읽고자하는 다짐으로 시작한다.

 

1. 생성자와 Static 팩토리 메소드의 제약

  매개변수가 많아질 경우 해당 클래스의 객체를 생성하려면 신축성 있게 처리하지 못한다.

 

2. 해결방법

가. 텔리스코핑 생성자 패턴

고전적인 방법으로 텔리스코핑 생성자(telescoping constructor) 패턴이 있다.

생성자를 오버로딩하여 사용하는 방법으로 코드로 표현해보면 아래와 같다.

 

public class Rects {
    private final int width;
    private final int height;
    private final int margin;
    private final int padding;
    public Rects(int width, int height){
        this(width, height, 0, 0);
    }
    public Rects(int width, int height, int margin){
        this(width, height, margin, 0);
    }
    public Rects(int width, int height, int padding){
        this(width, height, 0, padding);
    }
    public Rects(int width, int height, int margin, int padding){
        this.width = width;
        this.height = height;
        /..생략../
    }
}

 

위 클래스에서 width,height는 필수필드고 margin,padding은 선택필드이나 생성자를 오버로딩하여 작성했고 이 클래스의 객체(인스턴스)를 생성해보면 아래 코드와 같다.

 

Rects rect = new Rects(200,200,5,0);

 

텔리스코핑 생성자 패턴에서의 문제점은 객체를 생성하기 위해서 사용하지 않는 선택필드(위에서 padding을 0)의 값도 세팅을 해야만 하며 매개변수의 수가 증가하면 무척 번거로워진다는 것이다.

 

나. 자바빈즈(JavaBeans) 패턴

이 패턴은 생성자 호출 후 Setter 메소드를 이용하여 각각의 필수&선택 값을 지정한다.

흔히 사용하는 getter setter 클래스라고 생각하면 된다.

   

public class Rects {
    private final int width = 1;
    private final int height = 1;
    private final int margin = 0;
    private final int padding = 0;
    public Rects(){  }
    public void setWidth(int width){ this.width = width; }
    public void setHeight(int height ){ this.height = height ; }
    public void setMargin(int margin ){ this.margin = margin ; }
    public void setPadding(int padding ){ this.padding = padding ; }
}

 

위 자바빈즈 패턴 클래스의 객체(인스턴스)를 생성하는 코드는 아래와 같다.

Rects rect = new Rects();
rect.setWidth(200);
rect.setHeight(100);
rect.setMargin(5);

 

코드가 길어지기는 객체 생성이 간단하며 가독성이 좋다.

하지만 여기서 큰 단점이 있다.

객체 생성 시에 해당 객체가 완전하게 생성됨(책에서는 동결(freezing))을 보장할 수 없다.

예를 들어 Rects클래스의 객체 생성 시 width, height의 필수값이 세팅이 되어야 하지만

 

Rects rect = new Rects();
rect.setWidth(200);

 

위와 같이 width값만 생성 후 사용가능하다. 즉 동결시키는 메소드를 객체 사용 전에 호출했는지 컴파일러는 확인할 수 없기 때문이다.

 

다. 빌더(builder) 패턴

앞서 설명한 텔리스코핑 생성자 패턴과 자바빈즈 패턴을 결합한 빌더(builder) 패턴이 있다.

객체는 필수 매개변수를 갖는 생성자로 얻어낸 후 나머지 선택필드는 setter 메소드로 값을 세팅한다.

그리고 마지막으로 build 메소드를 호출하여 불변 객체를 생성한다.

(불변객체란 객체생성 후 상태가 변하지 않는다.)

 

public class Rects {
    private final int width;
    private final int height;
    private final int margin;
    private final int padding;
    
    public static class Builder {
        private final int width;
        private final int height;
        private int margin;
        private int padding;

        public Builder(int width, int height){
            this.width = width;
            this.height = height;
        }
        public Builder margin(int margin){
            this.margin = margin;
            return this;
        }
        public Builder padding(int padding){
            this.padding = padding;
            return this;
        }
        public Rects build(){
            return new Rects(this);
        }
    }

    public Rects(Builder builder){
        width = builder.width;
        height = builder.height;
        margin = builder.margin;
        padding = builder.padding;
    }

Builder 클래스의 setter 메소드를 통해 선택필드 값을 세팅하고 생성자에서 Rects 클래스의 생성자를 호출하여 값을 세팅하게 된다.

해당 클래스의 객체(인스턴스)를 생성하는 코드는 아래와 같다.

Rects rect = new Rects.Builder(200,100).margin(5).padding(10).build();

 

흡사 jquery의 메소드 체이닝 방법과 유사(?!)해 보인다. 위 Builder클래스의 setter 메소드를 보아도 선택필드의 값을 세팅 후 this를 리턴하고 있다. Javascript에서도 이와 같은 방법으로 메소드 체이닝 방법을 구현한다.

이 패턴의 장점으로는 코드의 작성이 쉽고 가독성이 좋다. 그리고 자바빈즈 패턴의 단점이던 매개변수의 불변규칙을 적용할 수 있다.

 

결론

생성자나 static 팩토리 메소드에서 많은 매개변수를 갖게 되는 클래스를 설계할 때는 필더 패턴이 좋은 선택이다.

저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글  2개가 달렸습니다.
  1. 요즘 effective 자바 책을 보는데요. 책 내용을 간단히 정리를 잘해놓으신거 같아요.
secret


Effective Java 이펙티브 자바Effective Java를 구매하여 매일 한 항목씩 읽으리라 다짐하며 공부하기 시작하였다. 

항목은 총 78항목(2판 기준)으로 큰 챕터는 아래와 같다.


1. 개요

2. 객체의 생성과 소멸

3. 모든 객체에 공통적인 메소드

4. 클래스와 인터페이스

5. 제네릭

6. 열거형과 주석

7. 메소드

8. 프로그래밍 일반

9. 예외

10. 동시성

11. 직렬화


이번에 정리하고자하는 내용은 챕터 2의 1항목인 "생성자 대신 static 팩토리(factory) 메소드 사용을 고려하자"이다.

블로그 작성 목적은 복습과 이 책을 다 읽고자하는 다짐으로 시작한다.


생성자

객체 기반의 프로그래밍을 하다보면 객체를 생성하여야 한다. 객체를 생성한다를 다른 표현으로 인스턴스를 생성한다라고 하며 해당 클래스의 객체를 생성하기 위해서는 public 생성자가 제공되어야 한다.

아래는 일반적인 객체 생성의 예이다.


Provider p = new Provider();


위와 같이 객체를 생성하기 위해서는 Provider 클래스에는 public 생성자가 필요하다는 것이다. 

이정도는 무수한 자바 기본서에 나오는 내용이고 이 책의 해당 항목은 생성자 대신 static 팩토리 메소드를 사용을 하란다.

그렇다면 static 팩토리 메소드는 멀까? 간단하다.


static 팩토리 메소드

아래는 책에서 나오는 Boolean 클래스의 예제이다.


public static Boolean valueOf(boolean b){
     return b ? Boolean.TRUE : Boolean.FALSE;
}

그냥 별거 없다... static 메소드이다. 


그렇다면 왜?? 생성자 대신에 쓰는게 좋을까??

장점1 생성자와 달리 자기 나름의 이름을 가질 수 있다.

   그렇다. 생성자는 클래스 이름과 동일해야 한다. static 팩토리 메소드를 사용하면 얼마든지 매개변수와 반환 객체를 

   잘 표현할 수 있는 메소드 네이밍이 가능하다.

   예를 들어 BigInteger.probablePrime 메소드가 있다.

   요점은 클래스에서 동일한 시그니처를 갖는 여러 개의 생성자가 필요한 경우에는 생성자 대신 static 팩토리 메소드를 

   사용하되, 메소드 간의 차이점을 부각시키도록 신중하게 이름을 선정하라는 거다.


장점2 생성자와 달리 호출될 때마다 매번 새로운 객체를 생성할 필요가 없다.

   이미 생성된 인스턴스를 다시 사용할 수 있으며, 불필요하게 중복된 인스턴스들이 생성되는 것을 방지한다.

   여기서 드는 생각은 싱글톤이다. 

   static 팩토리 메소드는 여러번 호출되더라도 이미 생성된 동일 객체를 반환할 수 있으며 이 기법은 Flyweight 패턴과 

   유사하다.


장점3 자신의 클래스 인스턴스만 반환하는 생성자와 달리 static 팩토리 메소드는 자신이 반환하는 타입의 어떤 서브타입    객체도 반환할 수 있다.

   읭??? 이건 또 무슨말이냐... 서브타입 객체?? 다형성에 관한 얘기인가???

   반환되는 객체의 클래스를 선택해야 할 때 유연하단다... 아직까지도 모르겠다.. 더 보자..

   자바 데이터베이스 연결 API로 이해할 수 있겠다.

   서비스 제공자(모듈)가 하나의 서비스를 구현하는 시스템으로써... 이말인 즉 슨 서비스란 인터페이스가 정의된 클래스라    생각할 수 있고 그 인터페이스에 따라 서비스 제공자는 서비스를 사용할 클라이언트를 위한 구현체(클래스 등)을 만든다.

   결국 인터페이스 다형성이라는 얘기네... 아 어려워... 나도 맞게 이해했나 모르겠다...

   결국 클라이언트는 내부에 감추어진 구현 클래스를 알 필요가 없으며 어떤 서브 클래스라는 것만 염두해 두면 된다.

   책의 예제기반으로 간략하게 코드로 표현하면(주요한 부분 이외는 많은 부분이 생략되어있다.)


//서비스 인터페이스
public interface Service{
}
//서비스 제공자 인터페이스
public interface Provider{
    Service newService();
}
public class Service {
    private Service() {} //인스턴스 생성 X

    public static Service newInstance(String name){
         //클라이언트에서 필요한 서비스 제공자에 맞는 제공자명을 넘겼을 경우 저장된 providers 저장공간에서 해당 할당받아 객체를 생성한다.
         Provider p = providers.get(name)
         //해당 제공자 객체에서 구현된 newService 메소드를 리턴

return p.newService(); } }

역시 소스를 보니 조금 이해가 된다...


장점4 매개변수화 타입의 인스턴스를 생성하는 코드를 간결하게 해준다.

  static 팩토리 메소드를 사용하면 컴파일러가 타입 매개변수를 해결해준다. 이를 타입 추론이라고 하며 아래 소스를 보면     이해가 쉽다.

  

Map<String, List<String>> m = new HashMap<String, List<String>>();

  

타이핑 힘들고 복잡하다.

 이때 HashMap에서 아래와 같은 static 팩토리 메소드가 제공된다고 가정하면 가정이다 가정 실제 존재하지는 않는다.

 

public static <K,V> HashMap<K,V> newInstance(){
    return new HashMap<K,V>();
}
Map<String, List<String>> m = new HashMap.newInstance();

타입 매개변수를 연달아 두번 주었지만 static 메소드 제공으로 한번으로 줄어듬을 확인할 수 있다.


그렇다면 단점은 어떤게 있을까?

단점1 인스턴스 생성을 위해 static 팩토리 메소드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 

  경우 서브 클래스를 가질 수 없다. 이럴 경우 상속이 불가능하하며 컴포지션 패턴 사용.

단점2 다른 static 메소드와 쉽게 구별할 수 없다.


결론

대부분 습관화된 public 생성자를 사용하는 것 보단 static 팩토리 메소드를 사용하는게 좋을 때가 많으며 우선 고려하는 습관을 가지는게 좋다.


저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글  4개가 달렸습니다.
  1. 비밀댓글입니다
    • 안녕하세요^^
      정말 좋은 책 2판이 새로나왔군요.
      요즘 제가 프로젝트 때문에 블로그나 책 읽을 시간이 없네요.
      책을 보내주셔도 당분간 제대로 읽을 시간이 없을 것 같습니다 ㅜㅜ 여유가 되면 꼭 구매 해서 읽어보도록 하겠습니다.
  2. 비밀댓글입니다
secret

Eclipse에서 Tomcat을 추가하려는데 아래와 같이 Server Name 입력 란에 키 입력이 되지 않는 현상이 발생하였다.




검색해보니 Eclipse 설정 파일을 몇개 삭제 해주면 문제해결 가능하더라.


1. 우선 Eclipse 을 종료한다.

2. Eclipse Workspace 아래의 설정폴더 내의 2개의 파일을 삭제한다.

  - {Workspace}/.metadata/.plugins/org.eclipse.core.runtime/.settings 폴더 내

    org.eclipse.wst.server.core.prefs

    org.eclipse.jst.server.tomcat.core.prefs

3. 삭제 후 Eclipse를 재시작하면 정상적으로 Tomcat을 추가 가능하다.


참고자료

저작자 표시
신고

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글  2개가 달렸습니다.
  1. 감사합니다 덕분에 해결됬어요
  2. 드디어 해결했네요 감사합니다.
secret

프로젝트를 진행하게되면 File Utils성 클래스를 만들어야 한다.


직접 만들기 보단 이전 프로젝트에서 가져오거나 인터넷 검색을 해서 이것저것 섞어 놓는 짜파게티 소스일 가능성이 크다.

이전 프로젝트에서 사용했던 소스라면 모르지만 인터넷 검색을 통해 얻은 검증되지 않은 코드를 사용하다보면 성능저하 및 에러의 원인이 되고는 한다.


물론 나도 현재 진행하는 프로젝트에서 위와 같은 방법으로 접근하였다.

어느정도 작업을 진행하던 중 Apache Commons IO라는 녀석을 알게되었다.


Commons IO는 아파치 소프트웨어 재단에서 제공하는 자바 기반의 IO관련 오픈소스이다.(Apache Commons Wiki)


아... 지금까지 코딩 및 인터넷 검색으로 이것저것 붙여넣기 하던 소스 휴지통으로 보내버리고 Commons IO를 사용하기로 했다.


지정된 경로의 텍스트 파일을 읽어 string으로 변환하는 소스로 비교해보면


내가 혹은 누군가가 만들었을 법 한 메소드

StringBuffer sb = new StringBuffer();  
BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(new File("/home/y2kpooh/test.txt")),"UTF8"));
String line;
while ((line = reader.readLine()) != null){
sb.
append(line);
}
reader.
close();

return sb.toString(); 


commons.io.FileUtils 사용

FileUtils.readFileToString(new File("/home/y2kpooh/test.txt")); 


누군가 만들어놓은걸 쓰면 참 쉽다..-_-;

이 외에도 IO관련 파일/폴더 이동 복사 비교 필터 등의 기능을 제공하며 


commons.io 소스를 다운로드 받아서 까보면 File 관련된 로직이 아주 잘 되어 있으니 참고하도록 하자


저작자 표시
신고

'java' 카테고리의 다른 글

Eclipse Tomcat 추가 시 Server Name blank 현상  (2) 2013.11.22
apache.commons.io.FileUtils  (1) 2013.07.24
JUnit을 이용한 단위 테스트  (3) 2013.01.15
OTN 자바 디벨로퍼 데이 in 서울  (0) 2011.02.21

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

받은 트랙백이 없고 , 댓글 하나 달렸습니다.
  1. 짜파게티 소스 ㅋㅋㅋ 공감되네요 ㅋㅋ
    기능 몇 개만 필요할 뿐인데 아파치 Commons를 쓰는 건 너무 과하지 않을까... 하고 생각해서 직접 만들어 써 보니 걍 공용 라이브러리 쓰는 게 훨씬 낫더랍니다.
secret

 

1. JUnit 이란

JUnit은 자바용 단위 테스트 작성을 위한 산업 표준 프레임워크다.

2. JUnit 환경 세팅

JUnit개발 가이드는 이클립스 + springMVC + maven 개발환경 기반으로 작성하였다.
혹 위 환경기반으로 프로젝트를 작성하지 않았다면 아래 개발환경 구축 내용을 확인하기 바람

이클립스 + 톰캣 스프링MVC + maven 개발환경 구축 1장
이클립스 + 톰캣 스프링MVC + maven 개발환경 구축 2장
이클립스 + 톰캣 스프링MVC + maven 개발환경 구축 3장



2.1 JUnit 라이브러리 추가
JUnit을 사용하려면 프로젝트에 JUnit 라이브러리가 필요하다.
Maven프로젝트는 의존관계 설정이 쉽게 되어 기존 프로젝트에서 처럼 개발자가 해당 라이브러리를 찾는 수고를 덜어준다.
Project Object Model(POM.xml) 에서 아래 그림1과 같이 Dependencies탭에서 JUnit을 찾아 추가를 하면 된다.

그림1



또는 직접 POM.xml에 dependencies element에 JUnit dependency를 아래와 같이 직접 추가할 수도 있다.

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.7</version>
  <scope>test</scope>
</dependency>



위와 같이 추가를 하게 되면 Maven 프로젝트 Install시 해당 라이브러리가 그림2와 같이 로컬 저장소에 저장하게 된다.

그림2




그림3

2.2 프로젝트 패키지 구성

JUnit테스트를 하기 위해서는 테스트 대상 클래스와 테스트 클래스는 같은 패키지 내에 있어야 한다. Maven 프로젝트를 생성하게 되면 Maven 관례에 따라, 그림3과 같은 프로젝트 템플릿이 기본적으로 생성된다. 그림3 디렉토리  /src/main/java/는 자바 코드를 보관하고
단위 테스트의 소스는 디렉토리 /src/test/java/ 디렉토리에 보관한다.
테스트 하고자 하는 클래스가 포함된 패키지명과 동일하게 테스트 클래스를 포함하는 패키지도 동일하게 구성한다. 테스트 대상 클래스와 테스트 클래스의 생성 예이다.



3. JUnit 테스트 클래스 작성

3.1 간략한 계산기 클래스 테스트

그림3의 프로젝트 패키지 구성에서 /src/main/java/ 디렉토리에
Calurator.java 클래스를 아래와 같이 작성한다.

package com.y2kpooh.junitTest;

public class Calcurator {
    public double sum(double a, doubleb){
        return a + b;
    }
}



위 Calcurator클래스는 double형의 두개의 파라메터를 받아 두 파라메터의 합을 구하여 double형으로 리턴해주는 sum 메서드를 가지고 있다.
물론 위 클래스는 문제가 될리 없는 간단한 프로젝트이나 테스트 클래스 작성에 이해를 돕기 위함이다.

Calurator클래스 작성 후 해당 클래스를 테스트 하기 위한 테스트 클래스를 작성해보자.
그림3의 프로젝트 패키지 구성에서 /src/test/java/ 디렉토리에 CaluratorTest.java 클래스를 아래와 같이 작성한다.

package com.y2kpooh.junitTest;

import org.junit.Test;
import static org.junit.Assert.*;

public class CaluratorTest {                                                  ←
    @Test                                                                                 
    public void testSum(){                                                        
        Calcurator c = new Calcurator();
        double result = c.sum(10, 50);                                     ←
        assertEquals(60, result, 0);                                           ←
    }
}



테스트 클래스는 반드시 public으로 선언해야 하며 클래스명은 관례에 따라 테스트클래명 + Test 끝나는 이름으로 사용된다. JUnit 3에서는 TestCase클래스를 상속받아 사용해야 했으나 JUnit 4에서는 상속받지 않아도 된다. (이 문서는 JUnit 4를 기반으로 작성되었다.)
@Test 어노테이을 선언하여 testSum 메서드가 단위 테스트 메서드임을 선언하였다.
클래스명과 마찬가지로 테스트 메서드는 test + 테스트메서드명으로 선언한다. @Test 어노테이션을 선언한 메서드는 JUnit이 알아서 실행을 해준다.
Calcurator 클래스의 인스턴스를 선언하여 sum 메서드에 10, 50 인자값을 세팅하여 result변수에 결과값을 리턴 받는다.
JUnit 프레임워크에의 Assert 클래스의 정적 메서드인 assertEquals를 이용하여 테스트 결과 값을 확인한다. assertEquals(expected, actual, delta)는 assertEquals(예상값, 실제값, 허용오차)

CalcuratorTest 클래스 테스트 결과 그림4와 같이 테스트 성공 결과가 나온다.

그림4


3.2 JUnit assert 주요 메서드 및 사용예시

assert 메서드

설명

assertArrayEquals(a, b); 배열 A와 B가 일치함을 확인한다.
assertEquals(a, b); 객체 A와 B가 일치함을 확인한다.
assertSame(a, b); 객체 A와 B가 같은 객임을 확인한다. assertEquals 메서드는 두 객체의 값이 같은가를 검사는데 반해 assertSame메서드는 두 객체가 동일한가 즉 하나의 객인 가를 확인한다.(== 연산자)
assertTrue(a); 조건 A가 참인가를 확인한다.
assertNotNull(a); 객체 A가 null이 아님을 확인한다.


위 메서드 외에도 많은 메서드와 오버로드된 메서드를 제공한다.
자세한 내용은 http://junit.sourceforge.net/javadoc/org/junit/Assert.html 해당 링크를 참고

String names[] = {"y2kpooh","hwang"};
String names2[] = {"y2kpooh","hwang"};
assertArrayEquals(names2, names);

List someList = someClass.getSomeList();
assertNotNull("조회결과 null", someList);
assertTrue(someList.size() > 0);
assertEquals(3, someList.size());




3.3 JUnit Annotation 사용 예시

스프링 프레임워크 기반의 JUnit 테스트를 위한 세팅

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:WebContent/WEB-INF/classes/applicationContext*.xml"})


Spring 기반의 테스트 코드 작성을 위해 테스트 클래스 상단에 @RunWith(SpringJUnit4ClassRunner.class) 구문을 추가한다.
Spring 프레임워크 context 파일을 테스트 수행시에도 동일하게 로딩하기 위해 @ContextConfiguration(locations={"file:WebContent/WEB-INF/classes/applicationContext*.xml"}) 과 같은 형태로 프로젝트의 스프링 설정파일을 설정해 준다.

메서드 수행시간 제한하기

@Test(timeout=5000)


단위는 밀리초이며 이 메서드가 결과를 반환하는데 5,000밀리초가 넘긴다면 테스트는 실패한다.

Exception 테스트

@Test(expected=RuntimeException.class)


해당 클래스는 RuntimeException이 발생해야 한다. 만약 테스트에서 RuntimeException이 발생하지 않을 경우 실패한다.

테스트 건너뛰기

@Test(timeout=5000)
@Ignore(value=”여기는 테스트 안할거야”)


@Ignore 어노테이션을 추가하면 해당 메서드는 테스트를 건너뛰게 되며 JUnit4는 성공 및 실패 개수와 함께 건너뛴 테스트 수도 포한된 결과 통계를 제공한다.



초기화 및 종료

@Before
[...]
@After
[...]


@Before 어노테이션이 선언된 메서드는 해당 테스트 클래스의 인스턴스, 객체를 초기하 하는 작업을 한다. @After 어노테이션이 선언된 메서드는 해당 테스트 실행 후 실행된다.
해당 @Before, @After 어노테이션 이외 @BeforeClass, @AfterClass도 있으며 이는 static 메서드와 동일한 형태로 테스트 클래스 실행 시 한번만 실행된다.


4. 목 객체를 활용한 테스트

4.1 목(Mock) 객체란?
 어플리케이션플리케이션을 개발하다보면, 테스트 대상 코드가 다른 클래스에 종속되어 있을 때가 종종 있다. 그 클래스가 다시 다른 클래스에 종속되고, 역시 또 다른 클래스에 종속되기도 한다.
JDBC를 통해 데이터베이스에 의존하는 JAVA EE 애플리케이션, 파일 시스템을 사용하는 어플리케이션, HTTP나 SOAP 등의 프로토콜로 외부 리소스에 접근하는 어플리케이션들을 예로 들 수 있다.
특정 런타임 환경에 의존적인 어플리케이션을 단위 테스트하는 것은 꽤나 고된 일이다.
테스트는 안정적이어야 하고, 반복적으로 수행해도 매번 동일한 결과를 내야 한다. 따라서 테스트를 올바로 수행하기 위해서는 환경을 제어할 수 있어야 한다.
예를 들어 다른 회사에 제공하는 웹 서버에 HTTP 커넥션을 맺는 어플리케이션을 제작하는 경우 그 외부 서버를 개발 환경 안에 가져올 수 없다. 테스트를 작성하고 돌려 보려면, 결국 서버를 시뮬레이션하는 방법이 필요하다.
또는 팀 단위 프로젝트에서 내가 맡은 부분을 테스트해보려 할때 다른 부분이 아직 준비되지 않았다면... 가짜를 만들어 미처 준비되지 못한 부분을 시뮬레이션할 수 있다.
이 처럼 가짜 객체를 제공하는 테스트 방식으로 목 객체를 사용할 수 있다.(스텁방식도 있다.)

4.2 목 객체를 활용해 단위 테스트 하기
한 은행 계좌에서 다른 계좌로 송금하는 단순한 테스트 케이스이다.

위 계좌 송금 프로세스를 기능 테스트 하기 위해서는 AccountService를 테스트 하기 위해서는 우선 데이터베이스를 세팅한 후 테스트 데이터를 채워넣는 작업을 진해하여야 한다.
목 객체를 활용하면 아직 작성되지 않은 코드를 테스트할 수 있다.
단 인터페이스가 정의되어 있어야 한다.

Account.java
계좌 ID와 잔고를 갖는 Account 객체

package com.y2kpooh.mock;

public class Account
{
   /**
    * 계좌 아이디
    */
   private String accountId;

   /**
    * 계좌 잔고
    */
   private long balance;

   /**
    * 초기화
    *
    * @param accountId
    * @param initialBalance
    */
   public Account( String accountId, long initialBalance )
   {
       this.accountId = accountId;
       this.balance = initialBalance;
   }

   /**
    * 출금
    *
    * @param amount
    */
   public void debit( long amount )
   {
       this.balance -= amount;
   }

   /**
    * 입금
    *
    * @param amount
    */
   public void credit( long amount )
   {
       this.balance += amount;
   }

   /**
    * 현재 잔고
    *
    * @return
    */
   public long getBalance()
   {
       return this.balance;
   }
}



AccountManager.java 인터페이스
Account 객체의 생명주기와 영속성을 관리한다.

package com.y2kpooh.mock;

public interface AccountManager
{
   /**
    * 아이디로 계좌 계정찾기
    *
    * @param userId
    * @return
    */
   Account findAccountForUser( String userId );

   /**
    * 계좌 계정 업데이트
    *
    * @param account
    */
   void updateAccount( Account account );
}



AccountService.java
두 계정 사이의 송금 기능을 제공한다. ID로 돈을 찾을 계좌와 받을 계좌를 찾고 정보를 갱신하기 위해 앞서 정의한 AccountManager 인터페이스를 활용한다.

package com.y2kpooh.mock;

public class AccountService
{
   /**
    * AccountManger 인터페이스 선언
    */
   private AccountManager accountManager;

   /**
    * 객체 초기화
    *
    * @param manager
    */
   public void setAccountManager( AccountManager manager )
   {
       this.accountManager = manager;
   }

   /**
    * 두 계좌 사이 송금기능
    *
    * @param senderId
    * @param beneficiaryId
    * @param amount
    */
   public void transfer( String senderId, String beneficiaryId, long amount )
   {
       Account sender = this.accountManager.findAccountForUser( senderId );
       Account beneficiary = this.accountManager.findAccountForUser( beneficiaryId );

       sender.debit( amount );
       beneficiary.credit( amount );
       this.accountManager.updateAccount( sender );
       this.accountManager.updateAccount( beneficiary );
   }
}



MockAccountManager.java
AccountService.transfer 메서드를 단위 테스트하고자 하므로 이를 위해 AccountManger 인터페이스의 목 객체를 구현해야 한다.

package com.y2kpooh.mock;

import java.util.Map;
import java.util.HashMap;

public class MockAccountManager implements AccountManager
{

   private Map<String, Account> accounts = new HashMap<String, Account>();

   /**
    * 아이디와 account 객체를 HashMap객체에 put
    *
    * @param userId
    * @param account
    */
   public void addAccount( String userId, Account account )
   {
       this.accounts.put( userId, account );
   }

   /**
    * 아이디로 HashMap객체에서 account 객체를 찾아 리턴
    */
   public Account findAccountForUser( String userId )
   {
       return this.accounts.get( userId );
   }

   /**
    * 계정 정보를 갱신하며 반환값은 없다.
    */
   public void updateAccount( Account account )
   {
       // do nothing
   }
}



TestAccountService.java
MockAccountManger를 이용하여 transfer 테스트하기

package com.y2kpooh.mock;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class TestAccountService
{
   @Test
   public void testTransferOk()
   {
       //테스트를 하기위한 객체 생성 및 준비
       MockAccountManager mockAccountManager = new MockAccountManager();
       Account senderAccount = new Account( "1", 200 );  
       Account beneficiaryAccount = new Account( "2", 100 );
       mockAccountManager.addAccount( "1", senderAccount );
       mockAccountManager.addAccount( "2", beneficiaryAccount );
       
       AccountService accountService = new AccountService();
       accountService.setAccountManager( mockAccountManager );
       // 테스트 수행
       accountService.transfer( "1", "2", 50 );
       // 결과 검증
       assertEquals( 150, senderAccount.getBalance() );
       assertEquals( 150, beneficiaryAccount.getBalance() );
   }
}



테스트 결과 및 프로젝트 패키지 구성화면

그림5




4.3 목 프레임워크 활용하기
목 객체를 활용하여 테스트 하려면 목 객체를 직접 개발자가 만들어야 한다.
바쁜 프로젝트 일정에 테스트하려고 목 객체를 만들자니 배보다 배꼽이 큰 것 같은 생각이 들지도 모른다. 역시나 천재들이 만들어 놓은 훌륭한 목 프레임워크가 존재 한다.
EasyMock과 JMock이 있으며 해당 라이브러리만 세팅하면 쉽게 목 객체를 활용할 수 있다.

<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.0</version>
</dependency>



테스트를 하고자 하는 클래스 AccountService는 이미 완성되어 있으며 해당 클래스를 테스트 하기 위해서는  AccountManager에 대한 Implement Class가 없다. 이 AccountManager에 대한 클래스를 EasyMock을 이용해서 테스트 가능하다.
(4.2 목 객체를 이용한 테스트 케이스에서는 AccountManager의 Implement Class로 MockAccountManager 클래스를 작성하여 테스트가 가능했었다. 여기서 꼭 기억할 것은 목 객체를 이용하여 테스트하기 위해서는 꼭 인터페이스가 정의되어 있어야 한다.)

easymock을 이용한 테스트 클래스 작성

package com.y2kpooh.mock;

import static org.junit.Assert.assertEquals;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.verify;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestAccountServiceEasyMock
{
   private AccountManager mockAccountManager;

   @Before
   public void setUp()
   {
       //목 객체를 생성한다.
       mockAccountManager = createMock("mockAccountManager", AccountManager.class );
   }

   @Test
   public void testTransferOk()
   {
       Account senderAccount = new Account( "1", 200 );
       Account beneficiaryAccount = new Account( "2", 100 );

       mockAccountManager.updateAccount( senderAccount );
       mockAccountManager.updateAccount( beneficiaryAccount );

       // 기대되는 행위 및 리턴 값 기록 한다.
       // expect : 기대되는 행위 메서드
       // addReturn : 리턴
       expect( mockAccountManager.findAccountForUser( "1" ) ).andReturn( senderAccount );
       expect( mockAccountManager.findAccountForUser( "2" ) ).andReturn( beneficiaryAccount );
       // 해당 목 객체를 수행한다.
       replay( mockAccountManager );

       AccountService accountService = new AccountService();
       accountService.setAccountManager( mockAccountManager);
       accountService.transfer( "1", "2", 50 );

       assertEquals( 150, senderAccount.getBalance() );
       assertEquals( 150, beneficiaryAccount.getBalance() );
   }

   @After
   public void tearDown()
   {
       // 테스트 실행
       verify( mockAccountManager);
   }
}

easymock에 대해서 더 자세히 알고 싶으시다면 아래 사이트를 참고하시기 바랍니다.
http://openframework.or.kr/framework_reference/easymock/2.3/Documentation_ko.html



                                                                               그림6

 

참고자료 : "JUnit in Action : 단위 테스트의 모든 것"

저작자 표시
신고

'java' 카테고리의 다른 글

Eclipse Tomcat 추가 시 Server Name blank 현상  (2) 2013.11.22
apache.commons.io.FileUtils  (1) 2013.07.24
JUnit을 이용한 단위 테스트  (3) 2013.01.15
OTN 자바 디벨로퍼 데이 in 서울  (0) 2011.02.21

WRITTEN BY
빵군
Web Programmer HOONS닷넷(http://www.hoons.kr) 2011 ASP.NET 시삽 http://about.me/y2kpooh

트랙백이 하나이고 , 댓글  3개가 달렸습니다.
  1. 감사합니다 퍼갈게요~
  2. 정말좋은글 퍼갈게요^^
  3. 감사합니다!
secret