2014년 2월 27일 목요일

Eclipse에 Maven(m2eclipse) 플러그인 설치 및 프로젝트 생성

1. m2eclipse 플러그인 설치

m2eclipse 플러그인을 사용하려면 먼저 이클립스의 Help >> Install New Software… 이동하여 m2eclipse 플러그인을 설치한다. m2eclipse 플러그인의 update url 은 http://m2eclipse.sonatype.org/sites/m2e 이다.

[ m2eclipse 플러그인을 설치하는 화면 ]


2. 프로젝트 생성
m2eclipse 플러그인을 설치하면 이클립스에서 메이븐 프로젝트를 생성하는 것이 가능하다. File >> New >> Project로 이동한 후 Maven 메뉴로 이동한 후 “Maven Project”를 선택한다.

[ 메이븐 프로젝트를 생성하는 화면 ]

위의 그림을 보면 버전 관리 시스템에 존재하는 프로젝트를 메이븐 프로젝트로 체크아웃하는 기능(Checkout Maven Projects from SCM)부터 여러 개의 모듈을 관리할 수 있는 기능(Maven Module)까지 제공하고 있다. 여기에서는 프로젝트를 생성하기 위하여 “Maven Project” 메뉴를 선택하고 “Next” 버튼을 선택한다.


[ 프로젝트 생성 경로를 선택하는 화면 ]

위의 화면에서의 기본 설정은 “Create a simple project(skip archetype selection)”이 체크되어 있지 않은 상태이다.

기본 설정으로 다음 단계로 넘어가면 아키타입의 대화식 모드를 이용하여 프로젝트를 생성하는 과정이 나온다. 메이븐에서 기본으로 제공하는 아키타입을 이용하여 프로젝트를 생성할 경우 불필요한 소스 코드를 제거하고 JUnit 의존 관계 설정을 제거해야 한다. 특히 maven-archetype-webapp 아키타입의 경우 소스 폴더가 자동으로 생성되지 않아 추가적으로 생성해야 하는 불편함이 있다.

이 같은 불편함을 없애고 단순히 메이븐 기본 디렉토리를 포함하는 프로젝트를 생성하고 싶다면 위의 그림과 같이 “Create a simple project(skip archetype selection)”를 체크한 후 “Next” 버튼을 누르고 프로젝트와 관련한 정보를 입력한다.


[ 위키북 프로젝트에 대한 기본 정보를 입력하는 화면 ]

위의 그림에서 보는 바와 같이 위키북 프로젝트에서 사용할 Group Id, Artifact Id, Version, Packaging을 입력하고 Finish 버튼을 눌러 프로젝트를 생성한다.

[ m2eclipse 플러그인을 활용하여 생성한 위키북 프로젝트 ]
m2eclipse 플러그인을 기반으로 프로젝트를 생성할 경우 위의 그림에서 보는 바와 같이 기본 디렉토리를 가지는 템플릿 프로젝트가 생성된다. 위키북 프로젝트의 pom.xml 파일을 열어보면 위에서 입력한 정보를 제외하고는 아무런 설정 정보도 추가되어 있지 않다.
<project ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.javajigi</groupId>
  <artifactId>wikibook</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>wikibook</name>
</project>


3. m2eclipse 플러그인 저장소 설정
m2eclipse 플러그인을 설치하면 위의 그림에서 볼 수 있듯이 pom.xml 파일과 프로젝트의 좌측 상단에 푸른색으로 “M”자가 표시된다. 이는 m2eclipse 플러그인이 메이븐 기본 설정 파일을 인식한다는 뜻이다.

m2eclipse 플러그인을 설치하면 M2_REPO Classpath Variables를 자동으로 추가한다. m2eclipse 플러그인을 설치한 후 Window >> Perferences >> Java >> Build Path >> Classpath Variable메뉴로 이동해 보면 그림 6-18과 같이 M2_REPO가 “non modifiable”로 추가되어 있는 것을 알 수 있다.

[ m2eclipse 플러그인을 설치한 후의 M2_REPO 설정 ]

M2_REPO 변수를 m2eclipse 플러그인이 관리하고 있기 때문에 수정할 수 없는 상태로 관리된다. 위의 그림에서 확인할 수 있듯이 M2_REPO 값은 기본 로컬 저장소인 USER_HOME/.m2/repository 값으로 설정된다. 만약 로컬 저장소를 변경해서 사용하고 있다면 다음과 같이 로컬 저장소를 변경해야 한다.

먼저 테스트를 위하여 USER_HOME/.m2/settings.xml 파일의 로컬 저장소 설정을 다음과 같이 “D:\m2\repository” 디렉토리로 설정한다.

# 로컬 저장소를 D:\m2\repository 디렉토리로 설정하는 settings.xml 파일
<settings ...>
<localRepository>D:\m2\repository</localRepository>
[...]
</settings>

기본 로컬 저장소를 사용하지 않으면 먼저 Window >> Perferences >> Maven >> User Settings 메뉴로 이동한 후 Update Settings 버튼을 클릭한다.

[ m2eclipse 플러그인에서 로컬 저장소의 경로를 변경하는 화면 ]

Update Settings 버튼을 클릭하면 Local Repository의 값이 위의 그림과 같이 “D:\m2\repository”로 변경된다. Update Settings 버튼을 클릭할 경우 Local Repository의 값이 바로 반영되지 않는 경우가 있다. 이 때는 다른 메뉴에 접근한 후 다시 User Settings 메뉴로 이동해보면 settings.xml 파일에 설정한 로컬 저장소로 반영되어 있는 것을 확인할 수 있다.

로컬 저장소의 경로를 변경할 경우 Window >> Perferences >> Java >> Build Path >> Classpath Variable 메뉴에 설정되어 있는 M2_REPO도 변경해야 한다. M2_REPO의 값을 새로 설정한 로컬 저장소로 변경하려면 앞의 과정을 통하여 로컬 저장소를 변경한 후 이클립스를 재시작해야 하면 자동으로 반영된다.


4. m2eclipse를 활용한 메이븐 실행

m2eclipse를 설치한 다음 pom.xml 파일에서 오른쪽 클릭 >> Run As로 이동하면 메이븐에서 자주 사용하는 페이즈와 골을 실행할 수 있다. m2eclipse를 활용하여 메이븐 명령을 실행하려면 먼저 USER_HOME\.m2 디렉토리에 settings.xml 파일을 생성해야 한다. 루트 엘리먼트(<settings/>)를 가지는 settings.xml 파일을 생성하거나 MAVEN_HOME\conf 디렉토리의 settings.xml 파일을 복사한다.

[ m2eclipse에서 실행 가능한 페이즈와 Goal ]
위의 그림을 보면 clean, install, package등 자주 사용하는 메이븐 페이즈를 실행할 수 있도록 지원하고 있다. 만약 Run As 메뉴에서 기본으로 제공하지 않는 페이즈나 골을 실행하려면 위의 그림에서 “Maven build...”을 실행한다. “Maven build...”을 실행하면 명령 프롬프트에서 메이븐을 실행할 때와 똑같이 특정 플러그인의 골을 직접 입력해 실행할 수 있다.

[ m2eclipse로 메이븐을 빌드하는 화면 ]

이 기능을 활용하면 명령 프롬프트에서 빌드할 필요없이 이클립스에서 메이븐을 빌드할 수 있다. 이클립스에서 명령 프롬프트로 이동하지 않아도 되므로 유용하다.

메이븐을 빌드할 때마다 이전에 빌드했던 “eclipse:clean eclipse:eclipse”를 입력하는 것도 귀찮은 작업이 된다. 이클립스는 이전에 실행한 모든 명령들에 대한 이력을 관리한다. m2eclipse에서 실행한 결과는 Run >> Run History메뉴나 이클립스 상단의 단축 아이콘으로 접근할 수 있다.

[ 이전 m2eclipse 빌드 이력을 보는 화면 ]

<참고사항>
내가 m2eclipse를 즐겨 사용할 때는 메이븐 기반 프로젝트를 빌드하기 위하여 자주 사용하는 명령어를 등록해 사용했다. 예를 들어 다음과 같이 명령어를 등록해 사용할 수 있다.
 wikibook for eclipse(이클립스 기반 프로젝트로 전환) : eclipse:clean eclipse:eclipse
 wikibook for build(프로젝트 빌드) : clean test

프로젝트의 빌드 상태에 따라서는 특정 Goal을 실행해야 하는 상황이 발생할 수 있기 때문에 자주 사용하는 페이즈와 골 조합을 위와 같이 등록해 사용하면 유용하다.
</참고사항>


5. m2eclipse를 활용한 설정 파일 및 의존 관계 관리

m2eclipse 플러그인을 활용할 때 유용하게 사용할 수 있는 기능이 지금까지 명령 프롬프트상에서 명령을 실행하고 결과물을 확인했던 내용을 이클립스에서 명령어를 몰라도 좀 더 좋은 UI를 통해 확인할 수 있다는 것이다.

메이븐 기반 프로젝트에서 pom.xml 파일을 더블 클릭하면 pom.xml 파일의 소스 코드가 바로 나타나지 않고 Overview 화면이 기본으로 열리며, 여러 개의 탭 화면을 가지는 것을 확인할 수 있다. 이 각각의 탭 중에서 유용하게 사용할 수 있는 몇 가지 기능에 대하여 살펴보도록 하겠다.

첫째, Dependencies 탭은 해당 프로젝트와 의존 관계에 있는 라이브러리를 관리할 수 있도록 지원한다. 5장에서 http://mvnrepository.com/ 사이트에 접속해 라이브러리를 검색하고 추가하던 작업을 m2eclipse를 활용해 가능하다.

[ Dependencies 탭에서 spring-core 라이브러리를 추가하는 화면 ]























2014년 2월 26일 수요일

Maven Plugins

Maven Plugins

1. Maven Plugin

  • 플러그인은 Maven의 핵심 기능으로 Goal이라는 실행 단위들의 집합으로 구성되어 있다.

기본적인 plugin 설정

  • 플러그인 정보는 build 엘리먼트의 plugins 엘리먼트 안에 설정한다.
  • 플러그인을 등록할 때 groupId가 생략된 경우도 있다. 기본으로 org.apache.maven.plugins 와 org.codehaus.mojo 는 생략해도 된다
    <plugins>
     ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      ..
    </plugins>
    
  • 아래와 같이 라이프사이클의 일부로 특정 Goal을 추가하고 설정 할 수 있다.
     
    <plugins>
     ...
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <configuration>
         <tasks>
           <echo>The JAVA_HOME var is ${env.JAVA_HOME}</echo>
         </tasks>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>run</goal>
            </goals>
            <phase>compile</phase>
          </execution>
        </executions>    
      </plugin>
      ..
    </plugins>
    
  • default lifecycle phase를 사용하는 예제
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <!-- Binds by default to the lifecycle phase: process-sources.  -->
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <excludeArtifactIds>servlet-api,jsp-api</excludeArtifactIds>
            <outputDirectory>${basedir}/web/WEB-INF/lib</outputDirectory>
            <overWriteReleases>false</overWriteReleases>
            <overWriteSnapshots>true</overWriteSnapshots>
          </configuration>
        </execution>
      </executions>
    </plugin>
    

2. Maven Plugins

문서정보

Maven Profiles - 서로 다른 빌드 환경간의 이식

Maven Profiles

  • Maven에 있는 Profile의 사용 목적은 서로 다른 빌드 환경간의 이식성 이다.

Profile 간단 사용 예제

  • profile 설정 간단 예제
<profiles>
  <profile>
    <id>local</id>
    <properties>
        <env>local</env>
    </properties>            
  </profile>
  <profile>
    <id>alpha</id>          
    <properties>
        <env>alpha</env>
    </properties>
  </profile>
  <profile>
    <id>beta</id>
    <properties>
        <env>beta</env>
    </properties>
  </profile>
  <profile>
    <id>release</id>
    <properties>
        <env>release</env>
    </properties>
    
    <activation>
        <property>
            <name>bds</name>
            <value>false</value>                    
        </property>                
    </activation>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>                    
                    <debug>true</debug>
                    <optimize>true</optimize>
                    <encoding>utf-8</encoding>
                    <showDeprecations>true</showDeprecations>
                    <fork>true</fork>                            
                    <executable>/usr/local/env/java/jdk1.5.0_10/bin/javac</executable>
                </configuration>
            </plugin>
        </plugins>
    </build>
  </profile>
  ..
</profiles>  

<properties>
    <env>local</env>
    <bds>false</bds>
    <clover.home>analysis_tools/clover</clover.home>
    <checkstyle.home>analysis_tools/checkstyle</checkstyle.home>
    <pmd.home>analysis_tools/pmd</pmd.home>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>      
</properties>

  • 실행시 profile 옵션을 사용한다 (ex. mvn package -Palpha)
  • 실행위치의 pom.xml, 실행위치의 profiles.xml, $USER_HOME/settings.xml, $M2_HOME/conf/settings.xml 순으로 profile 항목을 찾는다.
  • profile의 id 가 동일한 경우가 나타나면 해당 profile 에서 선언된 내용을 적용한다.
    즉 사용자 정의 환경변수 env 의 값이 alpha로 변경 됨.
  • mvn -Palpha는 mvn -Denv=alpha 로 변경 사용.

Profile activation의 사용

  • activation 엘리먼트는 오직 profile 엘리먼트 내부에서만 사용할 수 있다.
  • 정의한 환경과 실행 환경이 일치하면 profile에 설정한 값들이 project에 설정한 값에 대체 한다.
  • mvn -Ddeploy=true 실행시 아래 activation 수행 됨
     
    <profiles>
    
    <profile>
      <activation>
        <property>
            <name>deploy</name>
            <value>true</value>
        </property>
      </activation>
      <build>
        <plugins>       
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <tasks>
                                <mkdir dir="../webapps" />
                                <copy todir="../webapps" overwrite="true" preservelastmodified="true">
                                    <fileset  dir="${basedir}/web">
                                        <include name="**/*" />
                                    </fileset>
                                </copy>
                            </tasks>
                        </configuration>
                    </execution>
                </executions>
            </plugin>        
        </plugins>
      </build>
    </profile>
    </profiles>        
    

문서정보

Maven Properties - 동적인 환경구성

Maven Properties

  • pom.xml 파일에서 maven 프로퍼티를 사용하여 동적인 환경 구성이 가능하다.
  • 프로퍼티의 종류에는 project 프로퍼티, settings 프로퍼티, 환경 변수 프로퍼티, Java System 프로퍼티, 사용자 정의 프로퍼티가 있다.

1. Maven Project Properties

  • Maven Project Object Model(pom.xml) 에 정의된 값.
  • prefix. : project.*
        ex) ${project.build.outputDirectory}
      
<dependencies>
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>sibling-project</artifactId>
    <version>${project.version}</version>
  </dependency>
</dependencies> 
 

2. Maven Settings Properties

3. Environment Variable Properties

  • 환경 변수 값을 참조 할 수 있다.
  • prefix : env.*
  • 예제
    • env.PATH : 현재의 PATH 정보
    • env.JAVA_HOME : JDK Home 정보
    • env.HOME : 유닉스/리눅스 사용자 HOME 디렉토리
    • env.M2_HOME : Maven Home 정보
<!-- mvn antrun:run -->
<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <configuration>
    <tasks>
      <echo>The JAVA_HOME var is ${env.JAVA_HOME}</echo>
    </tasks>
  </configuration>
</plugin>

<!--  [echo] The JAVA_HOME var is C:\jdk1.5 출력된다. -->

4. Java System Properties

  • System.getProperty() 메소드에서 제공하는 모든 프로퍼티 정보를 참조할 수 있다.
Java System Properties설명
java.versionJRE의 버전
java.vendorJRE의 vendor 정보
java.homeJDK 또는 JRE의 설치 디렉토리
java.vm.specification.versionJVM SPEC 버전
java.vm.specification.nameJVM SPEC 이름
os.nameOS 이름
os.versionOS 버전
user.home사용자 홈 디렉토리
user.name사용자 이름
user.dir현재 디렉토리

5. User-defined Properties

  • 아래와 같이 직접 정의해서 사용 할 수 있다.
<project>
...
<properties>
  <env>local</env>
  <clover.home>analysis_tools/clover</clover.home>
  <checkstyle.home>analysis_tools/checkstyle</checkstyle.home>
  <pmd.home>analysis_tools/pmd</pmd.home>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>      
</properties>
...
</project>

6. Reference

문서정보

Maven Lifecycle 이란

Maven Lifecycle

1. Maven Lifecycle 이란

  • Maven에서는 clean, build, site의 세 가지 Lifecycle을 제공하고 있다.
  • 컴파일(compile), 테스트(test), 패키지(package), 배포(depooy)등의 과정은 빌드 Lifecycle에 속한다.
  • Maven은 모든 빌드 단위에 대한 Lifecycle이 예약되어 있어서 개발자가 임의로 변경 할 수 없다.
  • 각 Lifecycle은 순서를 갖는 단계(phase)로 구성된다.
  • Maven의 기본 Lifecycle을 이해하려면 Phase와 Goal의 개념을 이해해야 한다.




2. Phase와 Goal



2.1 Phase

  • Phase는 Build Lifecycle의 각각의 단계를 의미 한다.
  • Phase는 특정 순선에 따라서 goal이 실행되도록 구조를 제공 한다.
  • Phase 간에는 의존 관계가 있다.
    예를 들어 package phase가 수행되기 위해서는 이전 phase가 순서대로 수행된 다음에 실행된다.


2.2 Goal

  • Goal은 Ant의 Target과 같은 개념으로 생각하면 된다.

2.3 Phase와 Goal의 관계

  • Maven에서 제공하는 모든 기능은 플러그인 기반으로 동작한다.
  • Maven에서 기본으로 제공하는 Phase를 실행하면 해당 Phase와 연결된 플러그인의 Goal이 실행된다.
  • 각 phase는 0개 이상의 goal과 바인드 되어 있으며, 대부분 0또는 1개 이상의 Goal이 바인드 되어 있다.
  • Plugin Goal
    • Maven에서 플러그인을 실행할 때 '플러그인이름:플러그인지원골'의 형식으로 실행 할 기능을 선택 할 수 있다.
    • 예를들어 mvn compiler:compile은 'compiler' 플러그인에서 'compile' 기능(goal)을 실행한다는 것을 뜻 한다.
  • 아래는 package phase가 실행되기 전에 완료되야 하는 phase와 Goal이다.
PhaseGoal
[ resources:resources ]resources
[ compiler:compile ]compile
[ resources:testResources ]test-resources
[ compiler:testCompile ]test-compile
[ surefire:test ]test
[ jar:jar ]package
[표] 디폴트 라이프사이클의 주요 단계(phase)
 단계설명단계에 묶인 플러그인 실행
generate-sources컴파일 과정에 포함될 소스를 생성한다. 예를 들어,  DB 테이블과 매핑되는 자바 코드를 생성해주는 작업이 이 단계에서 실행된다.
process-sources필터와 같은 작업을 소스 코드에 처리한다. 
generate-resources패키지에 포함될 자원을 생성한다. 
process-resources필터와 같은 작업을 자원 파일에 처리하고, 자원 파일을 클래스 출력 디렉토리에 복사한다.resources:resources
compile소스 코드를 컴파일해서 클래스 출력 디렉터리에 클래스를 생성한다.compiler:compile
generate-test-sources테스트 소스 코드를 생성한다. 예를 들어, 특정 클래스에서 자동으로 테스트 케이스를 만드는 작업이 이 단계에서 실행된다.
process-test-sources필터와 같은 작업을 테스트 소스 코드에 처리한다.resources:testResources
generate-test-resources테스트를 위한 자원 파일을 생성한다. 
process-test-resources필터와 같은 작업을 테스트 자원 파일에 처리하고, 테스트 자원 파일을 테스트 클래스 출력 디렉터리에 복사한다. 
test-compile테스트 소스 코드를 컴파일해서 테스트 클래스 추력 디렉터리에 클래스를 생성한다.compiler:testCompile
test테스트를 실행한다.surefire:test
package컴파일 된 코드와 자원 파일들을 jar, war와 같은 배포 형식으로 패키징한다.패키징에 따라 다름
jar - jar:jar
war - war:war
pom - site:attach-descriptor
ejb - ejb:ejb
install로컬 리포지토리에 패키지를 복사한다.install:install
deploy생성된 패키지 파일을 원격 리포지토리에 등록하여, 다른 프로젝트에서 사용할 수 있도록 한다.deploy:deploy


3. Maven 디폴트 Phase와 Goal

process-resources

  • resources:resources Goal이 실행 된다.
  • Maven Resources Plugin
  • <resource> directory(/src/main/resources)를 <outputDirectory>에 생성 한다.

compile

  • resources:resources, compiler:compile Goal이 실행 된다.
  • Maven Compiler Plugin
  • 소스 코드를 컴파일해서 클래스를 <outputDirectory>에 생성 한다.

test-compile

  • compiler:compile, compiler:testCompile Goal이 실행 된다.
  • 테스트 소스 코드를 컴파일 한다.

test

  • compiler:compile, compiler:testCompile, surefire:test Goal이 실행 된다.
  • Maven Surefire Plugin
  • junit과 같은 테스트 코드를 실행, 테스트가 실패하면 빌드를 멈춘다
  • target/surefie-reports디렉토리 안에 test 리포트 파일을 생성한다
  • 단위 테스트 코드가 깨져도 빌드를 성공시키려면 maven.test.skip 속성을 true로 설정하면 된다.
    <properties>
            <maven.test.skip>true</maven.test.skip>
    </properties>       
     

package

  • package를 실행하면 compile, test-compile, test 순으로 실행된 다음 jar, war파일이 target 디렉토리 아래에 생성된다.
  • 패키징에 따라 아래의 플러그 인을 사용한다.
  • Maven Jar Plugin
  • Maven WAR Plugin
  • Maven EAR Plugin

install

deploy

  • 원격 리파지토리에 등록하여, 다른 프로젝트에서 사용 할 수 있도록 한다.
  • Maven Deploy Plugin

clean

Reference

문서정보

Maven 핵심개념 - Plugin,Goal,Phase,생명주기

원문 : http://demon92.tistory.com/14

1. Plugin,Goal

이번글에서는 maven에 관련된 몇가지 개념과 maven 생명주기에 대해서 정리해본다.
이전(http://demon92.tistory.com/12) 글에서 아래의 명령으로 기본적인 프로젝트를 생성했다

mvn archetype:generate -> archetype은 plugin, generate는 Goal이다.
단순하게 말해서 Maven은 설치만 해서는 아무것도 못한다. 즉 maven은 자체로는 아무런 처리능력이 없다. 몇개의 XML 문서를 파싱하고 생명주기와 몇가지 플러그인의 추적을 유지하는 것 이외에는 어떻게 처리할지 모른다.
maven으로 의미있는 작업을 하기위해서는 Plugin을 다운 받아야 한다.(명령을 실행하면 자동적으로 다운을 받는다).  Maven은 대부분의 책임을 maven 생명주기에 영향을 주고 goal에 대한 접근을 제공하는 Plugin에 위임하도록 되어 있다. Plugin은 여러개의 Goal를 가지고 있고 Goal은 Plugin에 포함되어 있는 명령이다. 즉 Maven의 Plugin은 하나이상의 Goal의 집합체이다. 

 
그럼 Goal은?  goal은 Maven에서 단위 작업 즉 명령이라고 생각하면 된다. 
소스코드를 compile하는 compiler Plugin의 compile goal 또는 단위 테스트를 실행할수 있는 Surefire Plugin의 test goal를 들수 있다. 아울러 goal은 기능을 변경하는데 사용될 수 있는 설정 속성을 설정된다.
예를 들면 Compiler plugin의 compile goal은 대상 JDK버전을 지정할수 있다 뒤에 이 부분은 다시 다룬다.

2. Phase

Phase를 이야기하기 전에 maven의 Lifecycle에 대해 간단히 알아보면 Maven Lifecycle은 프로젝트를 
빌드하는데 관련된 순차적인 단계들이다 ( maven defaule lifecycle(http://demon92.tistory.com/2) 참고)
Maven은 수많은 생명주기를 지원하지만 가장 자주 사용되는것은 기본 Mavne 생명주기로 프로젝트의 기본적인 통합을 검증하는 단계로 시작해서 제품으로 프로젝트를 배포하는 것과 관련된 단계로 끝난다.
                
            

Phase는  maven의 Build Lifecycle의 한부분으로 각각의 Phase는 어떤 일을 할지 정의하지 않고 어떤 Goal을 실행할지 설정한다. 즉 Plugin의 goal은 생명주기 단계에 붙을수 있다
package Phase에서 Jar Plugin의 jar goal를 실행한다.        
        

각각의 Phase는 그 안에 0개이상의 goal를 가질수 있다  package Phase는 jar packaging과 같은 프로젝트의 jar 파일을 생성하는것으로 알고 있지만 maven은 생명주기의 특정 Phase가 실행이 되면 그 이전의 
모든 Phase를 순차적으로 모두 수행한다.  즉 mvn package를 실행할때 maven은 package단계까지 모든 Phase를 실행하고 생명주기 Phase를 거치는 과정에서 해당 Phase에 속한 모든 goal들을 실행한다. 
(아래 그림 참조)

mvn package를 실행하는 대신에 다음과 같이 일련의 plugin goal들을 지정함으로써 동일한 결과를 얻을수 있다
mvn resources:resources compiler:compile resource:testResources compiler:testCompile surefire:test jar:jar

2014년 2월 25일 화요일

JUnit을 이용한 단위 테스트

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