4.1 개요
이 장은 3장에서 소개한 정보의 심화과정이다. Maven Archetype 플러그인으로 만들어진 단순한 프로젝트를 생성하고, 몇가지 의존관계를 추가하고, 소스 코드를 추가하고, 필요에 맞게 프로젝트를 변경할 것이다. 이 장의 끝에서 실제 프로젝트를 만드는데 메이븐 사용을 어떻게 시작하는지를 알게될 것이다.
야후의 날씨 웹 사이트와 상호작용하는 유용한 프로그램을 개발할 것이다. 예제 소스 코드 없이 이 장을 따라서 할 수도 있지만, 참고를 위해 사용되는 코드의 복사본을 다운로드받기를 권한다. 이 장의 예제 프로젝트는 http://www.sonatype.com/book/mvn-examples-1.0.zip 나 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 에서 책의 예제 코드와 같이 다운로드 받을 수 잇다. 디렉토리에서 압축을 풀고, ch04/ 디렉토리로 이동하라. 거기서 이 장에서 개발할 Maven 프로젝트가 포함되어 있는 simple-weather/ 디렉토리를 볼 것이다. 웹 브라우저에서 따라하기를 원한다면, http://www.sonatype.com/book/examples-1.0 를 방문하고 ch04/ 디렉토리로 이동한다.
4.2 간단한 Weather 프로젝트 정의
이 프로젝트를 본격적으로 시작하기 전에 간단한 weather 프로젝트에 대해 다시 한번 절차를 되짚어보고 설명하겠다. 이 프로젝트는 무엇인가? 메이븐의 몇가지 기능을 설명하기 위해 만들어진 고안된 예제이다. 빌드에 필요할 수 있는 종류에 대한 대표적인 어플리케이션이다. 간단한 weather 어플리케이션은 우편번호를 가지고 야후의 날씨 RSS 피드로부터 데이터를 추출하는 기초적인 명령행 유형 어플리케이션이다. 그 다음에 결과를 파싱하고 표준 출력으로 결과를 출력한다.
수많은 이유로 이 예제를 선택했다. 첫번째 직관적이다. 사용자는 명령행을 통해 입력을 제공하고, 어플리케이션은 입력된 우편번호를 가지고 야후 날씨에 요청을 보내고, 결과를 파싱하고, 화면에 단순한 데이터로 전환한다. 이 예제는 간단한 main() 함수이며, 몇가지 지원 클래스를 가진다. 소개나 설명이 필요한 전사 프레임워크가 없으며, 단지 XML 파싱과 몇가지 로깅 구문이 있다. 두번째로, Velocity, Dom4J, Log4J와 같은 몇가지 흥미로운 라이브러리를 소개하는 좋은 구실을 제공한다. 이 책은 메이븐에 초점을 맞추고 있지만, 흥미로운 유틸리티를 소개하는 기회를 멀리하지 않는다. 마지막으로, 단일 장에서 소개되고, 개발되고, 배포되는 예제이다.
이 어플리케이션을 빌드하기 전에 야후 날씨 RSS 피드에 대한 것을 알아둘 필요가 있다. 시작하기 위해 서비스는 다음의 용어하에서 사용가능하게 만들어졌다.
피드는 사적이고 비공식적인 사용을 위해 개인과 비수익 법인에 의한 사용을 목적으로 무료로 제공된다. 피드에 대한 사용으로 접속하려면 야후 날씨에 대한 속성을 제공해야 됨을 요청한다.
즉, 만일 상업용 웹 사이트에 이러한 흥미로운 피드를 통합하려고 생각하고 있다만, 다시 한번 생각하기 바란다. – 이 피드는 사적이고 비상업적인 용도이다. 이 장에서 사용하는 것은 사적인 교육용이다. 서비스 용어에 대한 더 많은 정보에 대해 http://developer.yahoo.com/weather/ 의 야후 날씨 API 문서를 참고하라.
4.3 간단한 Weather 프로젝트 생성
먼저 간단한 weather 프로젝트를 위한 기본적인 뼈대를 만들기 위해 Maven Archetype 플러그인을 사용해보자. 새로운 프로젝트를 생성하기 위해 다음의 명령을 실행한다.
일단 Maven Archetype 플러그인이 프로젝트를 생성하면, simple-weather 디렉토리로 이동해서 pom.xml 파일을 살펴본다. 예제 4-1에 나타난 XML 문서를 볼 수 있을 것이다.
archetype:create goal 에 version 파라미터를 넘기는 것에 유의하라. 이것은 1.0-SNAPSHOT 의 기본 값을 변경하는 것이다. 이 프로젝트에서 pom.xml 의 version 요소에서 볼 수 있듯이 simple-weather 프로젝트의 1.0 버전을 개발한다.
4.4 프로젝트 정보 수정
코드 작성을 시작하기 전에, 프로젝트 정보를 약간 변경해보자. 프로젝트의 라이선스, 조직, 프로젝트와 연관된 개발자들에 대한 정보를 추가하기를 원한다. 이는 대부분의 프로젝트에서 예상할 수 있는 모든 표준 정보들이다. 예제 4-2는 조직 정보, 라이센스 정보, 개발자 정보를 제공하는 XML을 보여주고 있다.
이 예제에서 점들은 생략된 부분이다. project 요소의 시작 태그 이후와 종료 태그 바로 직전에 "..."를 가진 pom.xml 을 볼 때마다, 전체 pom.xml 파일을 보여주지 않는 것으로 생각하면 된다. 이 경우, licenses, organization, developers 요소가 모두 dependencies 요소 전에 추가되었다.
4.5 새로운 의존관계 추가
간단한 날씨 어플리케이션은 다음 세가자 작업이 완료되는 것을 필요로 한다. 야후 날씨에서 XML 데이터 추출, 야후에서 XML 데이터 파싱, 표준 출력으로 변경된 형태의 결과를 출력. 이러한 작업을 수행하기 위해서 프로젝트의 pom.xml 에 새로운 의존관계가 몇가지 추가될 필요가 있다. 야후에서 XML 응답을 파싱하기 위해서 Dom4J와 Jaxen을 사용한다. 명령행 프로그램의 출력 형태를 변경하기 위해서 Velocity를 사용하며, 또한 로깅을 위한 사용으로 Log4J에 대한 의존 관계가 추가될 필요가 있다. 이러한 의존관계들을 추가한 후에 dependencies 요소는 예제 4-3처럼 보여질 것이다.
보는 바와 같이 JUnit에 대한 테스트 영역 의존관계를 참조했던 기존 요소에 추가로 4개를 더 dependency 요소를 추가했다. 프로젝트의 pom.xml 파일에 이러한 의존관계를 추가하고 나서 mvn install을 실행하면, 로컬 메이븐 레파지토리에 모든 이러한 의존관계들과 다른 연관 의존관계들을 메이븐 다운로딩하는 것을 볼 수 있을 것이다.
이러한 의존관계들은 어떻게 찾았는가? 단지 적절한 groupId와 artifactId 값만을 "알고" 있었는가? 어떤 의존관계들은(Log4J와 같은) 공통이어서 이들을 사용할 필요가 있을 때마다 groupId와 artifactId가 무엇인지를 기억할 것이다. Velocity, Dom4J, Jaxen에 대해서는 매우 도움이 되는 웹 사이트인 http://www.mvnrepository.com 을 사용해서 위치를 지정했다. 이 사이트는 의존관계를 검색하는데 사용할 수 있는 Maven 레파지토리에 대한 검색 인터페이스를 제공한다. 직접 테스트하려면, http://www.mvnrepository.com 를 방문하고 Hibernate나 Spring 프레임워크와 같은 자주 사용되는 라이브러리에 대한 검색을 해보라. 해당 사이트에서 산출물에 대한 검색을 했을 때 중앙 Maven 레파지토리에 알려진 artifactId와 모든 버전들을 볼 수 있을 것이다. 특정 버전에 대해 자세한 사용을 클릭하면 자신의 프로젝트의 pom.xml 에 복사/붙여넣기가 필요한 dependency 요소를 포함하는 페이지를 로딩한다. 의존관계를 찾을 필요가 있다면 http://www.mvnrepository.com 을 점검하기를 원할 것이다. 왜냐하면 어떤 라이브러리들은 하나 이상의 groupId를 가지는 것을 종종 찾게되기 때문이다. 이 도구를 통해 Maven 레파지토를 이해할 수 있을 것이다.
4.6 간단한 날씨 소스 코드
단순한 날씨 명령행 어플리케이션은 다음의 5개의 자바 클래스들로 구성된다.
여기에서 코드를 자세하게 설명하지는 않지만, 예제를 실행하는데 필요한 모든 코드를 제시할 것이다. 대부분의 독자들은 이 책에 있는 예제를 다운로드 받는다고 가정하지만, 이 장의 예제를 단계별로 따라하기 원할 수도 있는 독자들을 역시 염두한 것이다. 다음의 절은 simple-weather 프로젝트의 클래스들을 열거한다. 각각의 클래스들은 org.sonatype.mavenbook.weather 인 동일한 패키지에 위치해야 한다.
archetype:create로 생성된 App 와 AppTest 클래스를 지우고 새로운 패키지를 추가한다. 메이븐 프로젝트에서 모든 프로젝트 소스 코드는 src/main/java 에 저장된다. 새로운 프로젝트의 기존 디렉토리로부터 다음의 명령을 실행한다.
이는 org.sonatype.mavenbook.weather 라는 새로운 패키지를 생성한다. 이제 이 디렉토리에 클래스들을 넣을 필요가 있다. 선호하는 텍스트 편집기를 사용해서 예제 4-4에 나타난 내용이 있는 Weather.java 라는 새로운 파일을 생성한다.
Weather 클래스는 야후 날씨 피드로부터 파싱된 날씨 정보를 담는데 사용되는 단순한 빈을 정의한다. 이 피드는 일출/일몰 시간에서부터 풍속/풍향까지 많은 정보를 제공한다. 이 예제를 가능한 한 단순하게 하기 위해서, Weather 모델 객체는 현재 상태의 온도, 체감 온도, 습도, 문자 설명만을 유지한다.
이제 동일한 디렉토리에 Main.java 라는 파일을 생성한다. 이 Main 클래스는 이 예제의 진입점인 정적 main() 함수를 포함한다. 예제 4-5를 참조하라.
이 예제에 나타나는 main() 함수는 클래스패스로부터 리소스를 가지고 와서 Log4J를 설정한다. 그 다음에 명령행에서 우편번호를 읽으려고 한다. 우편번호를 읽으려고 하는 도중에 예외가 발생하면, 프로그램은 자동으로 60202 라는 우편번호를 세팅한다. 일단 우편번호를 읽으면, 메이븐의 인스턴스를 초기화하고 Main 인스턴스에 있는 start() 메소드를 호출한다. start() 메소드는 날씨 XML을 가지고 오기 위해 YahooRetriever 를 호출한다. YahooRetriever 는 InputStream 를 반환하고, 그 다음에 YahooParser 에게 넘긴다. YahooParser 는 야후 날씨 XML을 파싱하고 Weather 객체를 반환한다. 마지막으로 WeatherFormatter 은 Weather 객체를 가지고 표준 출력에 출력하는 String 형태로 반환한다.
동일한 디렉토리에 예제 4-6에 나타난 내용을 포함하는 YahooRetriever.java 라는 파일을 생성한다.
이 단순한 클래스는 야후 날씨 API로 URLConnection 을 열고 InputStream 을 반환한다. 이 피드를 파싱하는 것을 만들기 위해 동일한 디렉토리에 YahooParser.java 파일을 생성할 필요가 있다. 예제 4-7을 참고하라.
YahooParser 는 이 예제에서 가장 복잡한 클래스이다. 여기에서 Dom4J나 Jaxen에 대해 세부사항을 다루지 않을 것이지만, 클래스에 대해 설명할 가치가 있다. YahooParser의 parse() 메소드는 InputStream 을 입력받아 Weather 객체를 반환한다. 이렇게 하기 위해서 Dom4J를 사용해서 XML 문서를 파싱할 필요가 있다. 야후 날씨 XML 네임스페이스의 요소에 관심이 있기 때문에 네임스페이스를 인식하는 SAXReader 를 createXmlReader() 메소드에 만들 필요가 있다. 일단 이 메소드를 생성하고 문서를 파싱한다면, org.dom4j.Document 를 다시 받게 된다. 하위 요소를 통해 순환하는 대신에 XPath 표현을 사용해서 필요한 각각의 정보를 단순하게 뽑아낸다. Dom4J는 이 예제에서 XML 파싱을 제공하며, Jaxen은 XPath 기능을 제공한다.
일단 Weather 객체를 만들었다면, 사람이 읽을 수 있는 결과물로 형식을 바꿀 필요가 있다. 다른 클래스와 같이 동일한 디렉토리에 WeatherFormatter.java 라고 하는 파일을 생성한다. 예제 4-8을 참조하라.
WeatherFormatter 는 템플릿을 적용하기 위해 Velocity를 사용한다. format() 메소드는 Weather 빈을 입력받아 특정 형태의 String 으로 반환한다. format()가 수행하는 첫번째 일은 클래스패스로부터 output.vm 이라고 하는 Velocity 템플릿을 로딩한다. 그 다음에 weather 라고 하는 단일 Weather 객체를 가지는 VelocityContext 를 생성한다. StringWriter 는 템플릿 병합 결과를 담는데 생성된다. 템플릿은 그 다음에 Velocity.evaluate()를 호출하면서 수행되고, 결과가 String 으로 반환된다.
이 예제를 실행하기 전에 클래스패스에 몇가지 리소스를 추가할 필요가 있다.
4.7 리소스 추가
위의 프로젝트는 log4j.properties 라는 이름의 클래스패스 리소스를 갖는 Log4J를 설정하는 Main 클래스와, output.vm 라는 이름의 클래스패스로부터 Velocity 템플릿을 참고하는 WeatherFormatter 의 두개의 클래스패스 리소스에 의존관계를 가진다. 이 두가지 리소스는 기본 패키에 위치할 필요가 있다. (혹은 클래스의 루트)
이 리소스를 추가하려면, 프로젝트의 기본 디렉토리로부터 새로운 디렉토리인 src/main/resources 를 생성할 필요가 있다. 이 디렉토리는 archetype:create 작업으로 생성되지 않기 때문에 프로젝트의 기본 디렉토리로부터 다음의 명령을 실행함으로써 생성할 필요가 있다.
일단 리소스 디렉토리가 생성되면, 두개의 리소스를 추가할 수 있다. 먼저 예제 4-9에 나타난 것과 같이 resources 디렉토리에 log4j.properties 파일을 추가한다.
log4j.properties 파일은 PatternLayout을 사용해서 표준 출력에 모든 로그 메시지를 출력하는 Log4J를 단순하게 설정한다. 마지막으로 이 명령행 프로그램의 결과를 조회하는데 사용되는 Velocity 템플릿인 output.vm 를 생성할 필요가 있다. resources/ 디렉토리에 output.vm 를 생성한다. 예제 4-10을 참고하라.
위의 템플릿은 WeatherFormatter 으로 넘기는 Weather 빈인 weather라는 변수에 대한 수많은 참조를 포함한다. ${weather.temp} 는 temp 빈 속성에 대한 값을 가지고 보여주는 표시이다. 이제 적절한 지점에 모든 프로젝트 코드를 가지기 때문에 예제를 실행하기 위해 메이븐을 사용할 수 있다.
4.8 간단한 날씨 프로그램 실행
Codehaus Mojo project 로부터 Exec 플러그인을 사용해서 위의 프로그램을 실행할 수 있다. Main 클래스를 실행하려면, 프로젝트의 기본 디렉토리에서 다음의 명령을 실행한다.
Main 클래스에 명령행 인자를 입력하지 않았기 때문에, 기본 우편번호인 60202로 사용되었다. 우편번호를 입력하려면, -Dexec.args 인자를 사용해서 다음과 같이 우편번호를 넘길 수 있다.
보는 바와 같이 야후 날씨에서 데이터를 추출하고, 결과를 파싱하고, Velocity를 통해 결과 데이터를 출력하는 간단한 날씨 명령행 도구를 성공적으로 실행했다. 이러한 모든 것을 프로젝트의 소스 코드에 많은 것을 수행하지 않고 pom.xml에 최소한의 설정을 추가함으로써 수행했다. 어떠한 "빌드 절차"가 관여되지 않았음을 유의하라. Java 컴파일러가 어디에서 소스를 바이트코드로 컴파일하거나 어떻게 하는지를 정의할 필요가 없었으며, 예제 어플리케이션을 실행했을 때 바이트코드가 어떻게 위치해야하는지를 빌드 시스템에 지시할 필요가 없었다. 몇가지 의존관계만을 포함함으로써 적절한 메이븐 위상을 위치시키는 작업을 했을 뿐이다.
Exec 플러그인은 자바 클래스들과 다른 스크립트를 실행 가능하게 한다. 핵심 메이븐 플러그인은 아니지만, Codehaus에서 관리하는 Mojo 프로젝트에서 사용 가능하다. Exec 플러그인에 대한 전체 설명을 위해 다음을 실행한다.
이는 Maven Exec 플러그인에서 사용 가능한 모든 goal들을 나타낸다. Help 플러그인은 또한 Exec 플러그인에 대한 유효한 파라미터들을 모두 열거한다. 만일 명령행 인자에 넘기기 위해 Exec 플러그인의 행위를 변경하려면, 개발 동안 테스트를 실행하는 외부에서 어플리케이션을 실행하는 방식으로 의존해서는 안된다. 좀 더 강력한 해결책으로 이장 이후에 나오는 4.13 절에 설명된 Maven Assembly 플러그인을 사용한다.
Exec 플러그인은 클래스패스에 적합한 의존관계를 로딩할 필요없이 가장 간단한 날씨 프로그램을 실행도록 해준다. 다른 빌드 시스템에서 모든 프로그램 의존관계들을 JAR 파일의 집합체를 포함하는 lib/ 디렉토리와 같은 것에 복사해야 할 것이다. 그 다음에 클래스패스에 프로그램의 바이트코드와 모든 의존관계가 포함되는 간단한 스크립트를 작성해야만 할 것이다. 그 다음에 java org.sonatype.mavenbook.weather.Main을 실행할 수 있을 것이다. Exec 플러그인은 메이븐이 이미 클래스패스와 의존관계를 어떻게 생성하고 관리하는지를 알고 있다는 사실을 염두해둔다.
이는 편리하지만, 프로젝트의 클래스패스에 무엇이 포함되는지를 정확하게 아는 것 역시 좋다. 프로젝트가 Dom4J, Log4J, Jaxen, Velocity와 같은 몇가지 라이브러리들에 대해 의존관계를 갖지만, 몇가지 연결 의존관계 역시 의존하고 있다. 만일 클래스패스에 어떤 것들이 있는지를 알 필요가 있다면, 연결된 의존관계의 목록을 출력하는 Maven Dependency 플러그인을 사용할 수 있다. 간단한 날씨 프로젝트에 대해 이 목록을 출력하려면, 다음과 같이 dependency:resolve goal을 실행한다.
보는 바와 같이 프로젝트는 매우 많은 의존관계를 갖는다. 단지 4개의 라이브러리에만 직접 의존관계를 포함시켰지만, 총 15개의 의존관계가 나타나는 것을 볼 수 있다. Dom4J의 경우 Xerces와 XML Parser API에 대해 의존관계를 갖는 반면에, Jaxen은 클래스패스에서 사용 가능한 Xalan에 대해 의존관계를 갖는다. Dependency 플러그인은 프로젝트가 컴파일 되는 환경하에서 최종 의존관계에 대한 내용을 보여준다. 프로젝트에 대한 전체 의존관계 트리에 대해서 알고자 한다면, 다음과 같이 dependency:tree goal을 실행할 수 있다.
충돌이나 다른 이유로 인해 제거되는 산출물들을 포함하여 진정으로 전체 의존관계 추적을 보기를 원한다면, 다음과 같은 debug 플래그를 사용해서 메이븐을 실행한다.
debug 출력에서 작동하는 의존관계 관리 시스템의 내부를 보게 된다. 여기서 보는 것은 해당 프로젝트에 대한 의존관계 트리이다. 메이븐은 해당 프로젝트의 의존관계 전체와 그 의존관계들의 의존관계들에 대해서(그리고 의존관계의 의존관계에 대한 의존관계들) 전체 메이븐 위상을 출력한다. simple-weather가 jaxen이 xom에 대해 의존관계를 갖고, 차례로 xom이 icu4j에 대해 의존관계를 갖는 것을 볼 수 있다. 또한 중복을 제거하고, 서로 다른 버전간의 충돌을 해결하는 의존관계 그래프를 생성하는 것을 볼 수 있다. 의존관계에 대해 문제가 있다면, dependency:resolve에 의해서 생성되는 목록 보다 점 더 깊게 파고들어가는 것이 종종 도움이 된다. debug를 사용함으로써 작동하는 메이븐의 의존관계 체계를 볼 수 있다.
4.9. 단위 테스트 작성
메이븐은 단위 테스트에 대한 내장 지원을 가지고 있으며, 테스팅은 기본 메이븐 생명주기 중의 일부분이다. 간단한 날씨 프로젝트에 몇가지 단위 테스트들을 추가해보자. 먼저 src/test/java 밑에 org.sonatype.mavenbook.weather 패키지를 생성한다.
이 시점에서 두개의 단위 테스트를 생성할 것이다. 첫번째는 YahooParser 를 테스트하며, 두번째는 WeatherFormatter 를 테스트한다. weather 패키지에서 예제 4-11에 나타나는 내용을 포함하는 YahooParserTest.java 라는 파일을 생성한다.
YahooParserTest 는 JUnit에 의해 정의된 TestCase 클래스를 확장한다. JUnit 테스트에 대한 일반적인 패턴으로, 상위클래스의 생성자를 호출하는 단일 String 인자를 가지는 생성자와, 단위 테스트로 호출되는 “test”로 시작하는 여러 public 메소드를 가진다. 알려진 값들로 XML 문서를 파싱함으로써 YahooParser 를 테스트하는 단일 테스트 메소드인 testParser를 정의한다. 테스트 XML 문서는 ny-weather.xml 라고 하고 클래스패스로부터 로딩한다. 4.11 절에서 테스트 리소스를 추가할 것이다. Maven 프로젝트의 디렉토리 구조에서 테스트 리소스를 포함하는 디렉토리에서 ny-weather.xml 파일을 찾는다. – 즉 ${basedir}/src/test/resources 밑의 org/sonatype/mavenbook/weather/yahoo/ny-weather.xml. 파일은 InputStream 으로 읽어들여서 YahooParser의 parse() 메소드로 넘겨진다. parse() 메소드는 Weather 객체를 반환하며, 그 다음에 TestCase에 의해 정외된 메소드인 assetEquals()을 여러 번호출하여 테스트된다.
동일한 디렉토리에서 WeatherFormatterTest.java라는 파일을 생성한다. 예제 4-12를 참고하라.
이 간단한 프로젝트에서 두번째 단위 테스트는 WeatherFormatter을 테스트하는 것이다. YahooParserTest와 같이 WeatherFormatterTest 역시 JUnit의 TestCase 클래스를 상속받는다. 단일 테스트 함수는 단위 테스트의 클래스패스를 통해 ${basedir}/src/test/resources 밑의 org/sonatype/mavenbook/weather/yahoo 디렉토리로부터 동일한 테스트 리소스를 읽는다. 이 장의 이후의 4.11 절에서 테스트 리소스를 추가할 것이다. WeatherFormatterTest 은 Weather 객체를 나누는 YahooParser를 통해 이 예제 입력 파일을 실행하고, 그 다음에 객체는 WeatherFormatter 으로 형태가 변경된다. WeatherFormatter은 String을 출력하기 때문에, 예상되는 입력물에 대해 테스트할 필요가 있다. 예상되는 입력은 ny-weather.xml과 같은 디렉토리에 위치한 format-expected.dat 라는 텍스트 파일에 담겨진다. 테스트의 결과물을 예상 결과와 비교하려면, InputStream 으로 이 예상 결과를 읽어들여서 이 파일을 String 으로 변환하기 위해 Apache Commons IO의 IOUtils 클래스를 사용한다. String은 그 다음에 assertEquals()를 사용해서 테스트 출력과 비교된다.
4.10. 테스트 영역의 의존관계 추가
WeatherFormatterTest에서 Apache Commons IO로부터 IOUtils 클래스인 유틸리티를 사용했다. – IOUtils는 입력/출력 오퍼레이션으로부터의 대부분의 작업을 수행하는 수많은 도움이 되는 정적 함수를 제공한다. 위의 특정 단위 테스트에서 String 으로 format-expected.dat 클래스패스 리소를 복사하기 위해 IOUtils.toString()을 사용했다. 이를 Commons IO를 사용하지 않고 수행했지만, 다양한 InputStreamReader와 StringWriter 객체를 가지고 처리하려면 추가로 6~7 줄의 코드를 필요로 한다. Commons IO를 사용했던 주요 이유가 Commons IO에 대한 테스트 영역의 의존관계를 추가하려는 것이다.
테스트 영역의 의존관계는 테스트 컴파일과 실행에서만 클래스패스 상으로 사용 가능한 의존관계이다. 프로젝트가 war나 ear 패키징을 가지고 있다면, test 영역의 의존관계는 프로젝트의 결과물에 포함되지 않는다. test 영역의 의존관계를 추가하려면, 예제 4-13에 나타난 바와 같이 프로젝트의 <dependencies> 에 <dependency> 요소를 추가한다.
pom.xml에 위의 의존관계를 추가한 후에, mvn dependency:resolve를 실행하면 test 영역으로 의존관계로 포함된 commons-io를 볼 수 있을 것이다. 프로젝트의 단위 테스트를 실행기 전에 한가지 더 해야할 것이 있다. 이 단위 테스트가 의존하고 있는 클래스패스 리소스를 추가하는 것이다. 의존관계 영역은 9장의 9.4.1절에 상세하게 소개한다.
4.11. 단위 테스트 리소스 추가
단위 테스트는 테스트에 지정된 여러 리소스들에 대한 접근을 가진다. 종종 예상 결과를 포함하는 파일들과 테스트 클래스패스에 임의의 입력을 포함하는 파일들을 저장할 것이다. 위의 프로젝트에서 YahooParserTest 에 대한 테스트 XML 문서로 ny-weather.xml 라는 파일과 format-expected.dat 에 WeatherFormatter로 부터의 예상 출력을 포함하는 파일을 저장한다.
테스트 리소스를 추가하려면, src/test/resources 디렉토리를 생성할 필요가 있다. 이는 Maven 이 단위 테스트 리소스를 찾는 기본 디렉토리이다. 이 디렉토리를 생성하려면, 프로젝트의 기본 디렉토리에서 다음의 명령을 실행한다.
일단 resources/ 디렉토리를 생성했다면, 거기서 format-expected.dat라는 파일을 생성한다. 예제 4-14를 참고하라.
위의 파일은 유사하게 보여야 한다. Maven Exec 플러그인을 사용해서 간단한 날씨 프로젝트를 실행했을 때 이전에 생성되었던 결과와 동일하다. 리소스 디렉토리에 추가할 두번째 파일은 ny-weather.xml이다. 예제 4-15를 참고하라.
위의 파일은 YahooParserTest을 위한 테스트 XML 문서를 포함한다. 이 파일을 저장함으로써 야후 날씨로부터 XML 응답을 조회하지 않고 YahooParser를 테스트할 수 있다.
4.12. 단위 테스트 실행
이제 프로젝트가 단위 테스트가 갖추어졌으므로 이를 실행해보자. 단위 테스트를 실행하기 위해서 특별한 것을 할 필요가 없다. test 단계는 메이븐 생명주기의 일반적인 부분이다. mvn package나 mvn install을 실행할 때마다 Maven test를 실행한다. test 단계를 포함하여 모든 생명주기 단계를 실행하기 원하면, 다음과 같이 mvn test을 실행한다.
명령행에서 mvn test를 실행하는 것은 Maven이 test 단계까지 모든 생명주기 단계를 실행하도록 하는 것이다. Maven Surefire 플러그인은 test 단계에 포함된 test goal을 가지고 있다. 이 test goal은 해당 프로젝트가 src/test/java에서 찾을 수 있는 모든 단위 테스트를 실행한다. 이 프로젝트의 경우에, Surefire 플러그인의 test goal이 WeatherFormatterTest와 YahooParserTest를 실행하는 것을 볼 수 있다. Surefire 플러그인이 JUnit 테스트를 실행할 때 ${basedir}/target/surefire-reports 디렉토리에 XML과 테스트 리포트를 생성한다. 테스트가 실패하면 단위 테스트에 의해 생성된 스택 트레이스와 에러 메시지와 같은 세부사항에 대해 이 디렉토리를 살펴볼 수 있다.
단위 테스트를 실패하는 시스템에 대한 개발을 종종 목격할 것이다. TDD (Test-Driven Development)를 적용하고 있다면, 테스트 실패를 프로젝트가 얼마나 완벽에 가까운지에 대한 측정으로 사용할 수도 있다. 실패한 단위 테스트를 가지고 있고, 그럼에도 불구하고 빌드 결과를 만들고자 한다면, 메이븐에게 빌드 실패를 무시하라고 전달할 필요가 있을 것이다. 메이븐이 빌드 실패를 만났을 때 기본 행위는 현재 빌드를 멈추는 것이다. Surefire 플러그인이 실패한 테스트 케이스를 만나더라도 프로젝트 빌드를 계속하려면, Surefire 플러그인의 testFailureIgnore 설정 속성에 true 세팅한다. 예제 4-16을 참고하라.
플러그인 문서(http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html)는 예제 4-17과 같이 이 파라미터가 표현식으로 선언됨을 보여준다.
이 표현식은 다음과 같이 –D 파라미터를 사용해서 명령행에서 세팅될 수 있다.
단위 테스트를 보두 건너뛰도록 Maven을 설정하기 원할 수도 있다. 아마도 단위 테스트가 완료될 때까지 수분이 걸려서 결과를 만들기 전에 단위 테스트를 기다리기를 원치 않는 매우 큰 시스템을 가질 수도 있다. 혹은 여러 차례 실패하는 단위테스트를 가지는 기존 시스템으로 작업할 때 이를 수정하기 보다는 JAR 를 만들기를 원하는 경우일 수도 있다. Maven은 Surefire 플러그인의 skip 파라미터를 사용해서 단위 테스트들을 건너띄게 만들 수 있다. 명령행에서 테스트를 건너뛰려면, goal에 maven.test.skip 속성을 추가하면 된다.
Surefire 플러그인이 test goal에 도달했을 때 maven.test.skip 속성이 true로 세팅되었다면 단위 테스트를 건너뛴다. 단위 테스트를 건너뛰도록 메이븐을 설정하는 다른 방법은 예제 4-18에 나타난 것처럼 프로젝트의 pom.xml 에 설정을 추가하는 것이다. 이렇게 하려면 <build> 설정에 <plugin> 요소를 추가하면 된다.
4.13. 패키징된 명령행 어플리케이션의 빌드
이 책의 이전 4.8절에서 Maven Exec 플러그인을 사용해서 어플리케이션을 실행했다. 이 플러그인이 프로그램을 실행하고 어떤 결과를 만들었지만, 메이븐을 어플리케이션에 대한 실행 컨테이너로 생각해서는 안된다. 이 명령행 어플리케이션을 다른 사람들에게 배포한다면 JAR나 ZIP 혹은 TAR된 GZIP 파일의 압축파일로 배포하기 원할 것이다. 이 절은 프로젝트의 바이트코드와 모든 의존관계를 포함하는 배포가능한 JAR 파일로 만들어내기 위해 Maven Assembly 플러그인에 사전에 정의된 assembly descriptor 사용에 대한 절차를 보여준다.
어플리케이션에 대한 임의의 배포를 만들기 위해 Maven Assembly 플러그인을 사용할 수 있다. 사용자에 입맛에 맞는 assembly descriptor를 정의함으로써 원하는 형태로 프로젝트의 결과를 조합하는데 사용할 수 있다. 이 장에서 간단한 날씨 어플리케이션에 대해 좀 더 복잡한 형태를 만들어내는 변경된 assembly descriptor를 어떻게 만드는지를 보여줄 것이다. 이 장에서 사전에 정의된 jar-with-dependencies 형태를 사용하려고 한다. Assembly 플러그인을 설정하려면, pom.xml 에 기존 빌드 설정에 예제 4-19에 나타난 <plugin> 설정을 추가할 필요가 있다.
일단 위의 설정을 추가하면, 아래와 같이 mvn assembly:assembly를 실행해서 assembly를 빌드할 수 있다.
일단 assembly가 target/simple-weather-1.0-jar-with-dependencies.jar에 만들어지면, 명령행에서 다시 Main 클래스를 실행할 수 있다. 간단한 날씨 어플리케이션의 Main 클래스를 실행하려면, 프로젝트의 기반 디렉토리에서 다음을 실행한다.
jar-with-dependencies 형태는 간단한 날씨 프로젝트의 모든 바이트코드 뿐만 아니라 모든 의존관계의 압축된 바이트코르를 포함하는 단일 JAR 파일을 생성한다. 이러한 약간은 불편한 형태가 약 5,290 여개의 클래스를 포함하는 9MiB 짜리 JAR 파일을 만들어내지만, 메이븐으로 만든 어플리케이션에 대해 편리한 배포 형태로 제공한다. 이 책의 이후에 더 표준적인 배포를 만들어내는 assembly descriptor를 어떻게 생성하는지를 보여줄 것이다.
<<Pre Next>>
Elvis Lee에 의해 창작된 메이븐 가이드 은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이선스에 따라 이용할 수 있습니다.
www.sonatype.com의 저작물에 기초
이 장은 3장에서 소개한 정보의 심화과정이다. Maven Archetype 플러그인으로 만들어진 단순한 프로젝트를 생성하고, 몇가지 의존관계를 추가하고, 소스 코드를 추가하고, 필요에 맞게 프로젝트를 변경할 것이다. 이 장의 끝에서 실제 프로젝트를 만드는데 메이븐 사용을 어떻게 시작하는지를 알게될 것이다.
4.1.1 이 장의 예제 다운로딩
야후의 날씨 웹 사이트와 상호작용하는 유용한 프로그램을 개발할 것이다. 예제 소스 코드 없이 이 장을 따라서 할 수도 있지만, 참고를 위해 사용되는 코드의 복사본을 다운로드받기를 권한다. 이 장의 예제 프로젝트는 http://www.sonatype.com/book/mvn-examples-1.0.zip 나 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 에서 책의 예제 코드와 같이 다운로드 받을 수 잇다. 디렉토리에서 압축을 풀고, ch04/ 디렉토리로 이동하라. 거기서 이 장에서 개발할 Maven 프로젝트가 포함되어 있는 simple-weather/ 디렉토리를 볼 것이다. 웹 브라우저에서 따라하기를 원한다면, http://www.sonatype.com/book/examples-1.0 를 방문하고 ch04/ 디렉토리로 이동한다.
4.2 간단한 Weather 프로젝트 정의
이 프로젝트를 본격적으로 시작하기 전에 간단한 weather 프로젝트에 대해 다시 한번 절차를 되짚어보고 설명하겠다. 이 프로젝트는 무엇인가? 메이븐의 몇가지 기능을 설명하기 위해 만들어진 고안된 예제이다. 빌드에 필요할 수 있는 종류에 대한 대표적인 어플리케이션이다. 간단한 weather 어플리케이션은 우편번호를 가지고 야후의 날씨 RSS 피드로부터 데이터를 추출하는 기초적인 명령행 유형 어플리케이션이다. 그 다음에 결과를 파싱하고 표준 출력으로 결과를 출력한다.
수많은 이유로 이 예제를 선택했다. 첫번째 직관적이다. 사용자는 명령행을 통해 입력을 제공하고, 어플리케이션은 입력된 우편번호를 가지고 야후 날씨에 요청을 보내고, 결과를 파싱하고, 화면에 단순한 데이터로 전환한다. 이 예제는 간단한 main() 함수이며, 몇가지 지원 클래스를 가진다. 소개나 설명이 필요한 전사 프레임워크가 없으며, 단지 XML 파싱과 몇가지 로깅 구문이 있다. 두번째로, Velocity, Dom4J, Log4J와 같은 몇가지 흥미로운 라이브러리를 소개하는 좋은 구실을 제공한다. 이 책은 메이븐에 초점을 맞추고 있지만, 흥미로운 유틸리티를 소개하는 기회를 멀리하지 않는다. 마지막으로, 단일 장에서 소개되고, 개발되고, 배포되는 예제이다.
4.2.1. 야후 날씨 RSS
이 어플리케이션을 빌드하기 전에 야후 날씨 RSS 피드에 대한 것을 알아둘 필요가 있다. 시작하기 위해 서비스는 다음의 용어하에서 사용가능하게 만들어졌다.
피드는 사적이고 비공식적인 사용을 위해 개인과 비수익 법인에 의한 사용을 목적으로 무료로 제공된다. 피드에 대한 사용으로 접속하려면 야후 날씨에 대한 속성을 제공해야 됨을 요청한다.
즉, 만일 상업용 웹 사이트에 이러한 흥미로운 피드를 통합하려고 생각하고 있다만, 다시 한번 생각하기 바란다. – 이 피드는 사적이고 비상업적인 용도이다. 이 장에서 사용하는 것은 사적인 교육용이다. 서비스 용어에 대한 더 많은 정보에 대해 http://developer.yahoo.com/weather/ 의 야후 날씨 API 문서를 참고하라.
4.3 간단한 Weather 프로젝트 생성
먼저 간단한 weather 프로젝트를 위한 기본적인 뼈대를 만들기 위해 Maven Archetype 플러그인을 사용해보자. 새로운 프로젝트를 생성하기 위해 다음의 명령을 실행한다.
$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch04 \
-DartifactId=simple-weather \
-DpackageName=org.sonatype.mavenbook \
-Dversion=1.0
[INFO] [archetype:create]
[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: \
checking for updates from central
[INFO] ------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: \
maven-archetype-quickstart:RELEASE
[INFO] ------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch04
[INFO] Parameter: packageName, Value: org.sonatype.mavenbook
[INFO] Parameter: basedir, Value: ~/examples
[INFO] Parameter: package, Value: org.sonatype.mavenbook
[INFO] Parameter: version, Value: 1.0
[INFO] Parameter: artifactId, Value: simple-weather
[INFO] *** End of debug info from resources from generated POM ***
[INFO] Archetype created in dir: ~/examples/simple-weather
-DartifactId=simple-weather \
-DpackageName=org.sonatype.mavenbook \
-Dversion=1.0
[INFO] [archetype:create]
[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: \
checking for updates from central
[INFO] ------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: \
maven-archetype-quickstart:RELEASE
[INFO] ------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch04
[INFO] Parameter: packageName, Value: org.sonatype.mavenbook
[INFO] Parameter: basedir, Value: ~/examples
[INFO] Parameter: package, Value: org.sonatype.mavenbook
[INFO] Parameter: version, Value: 1.0
[INFO] Parameter: artifactId, Value: simple-weather
[INFO] *** End of debug info from resources from generated POM ***
[INFO] Archetype created in dir: ~/examples/simple-weather
일단 Maven Archetype 플러그인이 프로젝트를 생성하면, simple-weather 디렉토리로 이동해서 pom.xml 파일을 살펴본다. 예제 4-1에 나타난 XML 문서를 볼 수 있을 것이다.
예제 4-1. simple-weather 프로젝트에 대한 초기 POM
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook.ch04</groupId>
<artifactId>simple-weather</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>simple-weather2</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook.ch04</groupId>
<artifactId>simple-weather</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>simple-weather2</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
archetype:create goal 에 version 파라미터를 넘기는 것에 유의하라. 이것은 1.0-SNAPSHOT 의 기본 값을 변경하는 것이다. 이 프로젝트에서 pom.xml 의 version 요소에서 볼 수 있듯이 simple-weather 프로젝트의 1.0 버전을 개발한다.
4.4 프로젝트 정보 수정
코드 작성을 시작하기 전에, 프로젝트 정보를 약간 변경해보자. 프로젝트의 라이선스, 조직, 프로젝트와 연관된 개발자들에 대한 정보를 추가하기를 원한다. 이는 대부분의 프로젝트에서 예상할 수 있는 모든 표준 정보들이다. 예제 4-2는 조직 정보, 라이센스 정보, 개발자 정보를 제공하는 XML을 보여주고 있다.
예제 4-2. 조직, 라이센스, 개발자 정보를 pom.xml에 추가
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
...
<name>simple-weather</name>
<url>http://www.sonatype.com</url>
<licenses>
<license>
<name>Apache 2</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>
<organization>
<name>Sonatype</name>
<url>http://www.sonatype.com</url>
</organization>
<developers>
<developer>
<id>jason</id>
<name>Jason Van Zyl</name>
<email>jason@maven.org</email>
<url>http://www.sonatype.com</url>
<organization>Sonatype</organization>
<organizationUrl>http://www.sonatype.com</organizationUrl>
<roles>
<role>developer</role>
</roles>
<timezone>-6</timezone>
</developer>
</developers>
...
</project>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
...
<name>simple-weather</name>
<url>http://www.sonatype.com</url>
<licenses>
<license>
<name>Apache 2</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>
<organization>
<name>Sonatype</name>
<url>http://www.sonatype.com</url>
</organization>
<developers>
<developer>
<id>jason</id>
<name>Jason Van Zyl</name>
<email>jason@maven.org</email>
<url>http://www.sonatype.com</url>
<organization>Sonatype</organization>
<organizationUrl>http://www.sonatype.com</organizationUrl>
<roles>
<role>developer</role>
</roles>
<timezone>-6</timezone>
</developer>
</developers>
...
</project>
이 예제에서 점들은 생략된 부분이다. project 요소의 시작 태그 이후와 종료 태그 바로 직전에 "..."를 가진 pom.xml 을 볼 때마다, 전체 pom.xml 파일을 보여주지 않는 것으로 생각하면 된다. 이 경우, licenses, organization, developers 요소가 모두 dependencies 요소 전에 추가되었다.
4.5 새로운 의존관계 추가
간단한 날씨 어플리케이션은 다음 세가자 작업이 완료되는 것을 필요로 한다. 야후 날씨에서 XML 데이터 추출, 야후에서 XML 데이터 파싱, 표준 출력으로 변경된 형태의 결과를 출력. 이러한 작업을 수행하기 위해서 프로젝트의 pom.xml 에 새로운 의존관계가 몇가지 추가될 필요가 있다. 야후에서 XML 응답을 파싱하기 위해서 Dom4J와 Jaxen을 사용한다. 명령행 프로그램의 출력 형태를 변경하기 위해서 Velocity를 사용하며, 또한 로깅을 위한 사용으로 Log4J에 대한 의존 관계가 추가될 필요가 있다. 이러한 의존관계들을 추가한 후에 dependencies 요소는 예제 4-3처럼 보여질 것이다.
예제 4-3. 의존관계로 Dom4J, Jaxen, Velocity, Log4J 추가
<project>
[...]
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
[...]
</project>
[...]
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
[...]
</project>
보는 바와 같이 JUnit에 대한 테스트 영역 의존관계를 참조했던 기존 요소에 추가로 4개를 더 dependency 요소를 추가했다. 프로젝트의 pom.xml 파일에 이러한 의존관계를 추가하고 나서 mvn install을 실행하면, 로컬 메이븐 레파지토리에 모든 이러한 의존관계들과 다른 연관 의존관계들을 메이븐 다운로딩하는 것을 볼 수 있을 것이다.
이러한 의존관계들은 어떻게 찾았는가? 단지 적절한 groupId와 artifactId 값만을 "알고" 있었는가? 어떤 의존관계들은(Log4J와 같은) 공통이어서 이들을 사용할 필요가 있을 때마다 groupId와 artifactId가 무엇인지를 기억할 것이다. Velocity, Dom4J, Jaxen에 대해서는 매우 도움이 되는 웹 사이트인 http://www.mvnrepository.com 을 사용해서 위치를 지정했다. 이 사이트는 의존관계를 검색하는데 사용할 수 있는 Maven 레파지토리에 대한 검색 인터페이스를 제공한다. 직접 테스트하려면, http://www.mvnrepository.com 를 방문하고 Hibernate나 Spring 프레임워크와 같은 자주 사용되는 라이브러리에 대한 검색을 해보라. 해당 사이트에서 산출물에 대한 검색을 했을 때 중앙 Maven 레파지토리에 알려진 artifactId와 모든 버전들을 볼 수 있을 것이다. 특정 버전에 대해 자세한 사용을 클릭하면 자신의 프로젝트의 pom.xml 에 복사/붙여넣기가 필요한 dependency 요소를 포함하는 페이지를 로딩한다. 의존관계를 찾을 필요가 있다면 http://www.mvnrepository.com 을 점검하기를 원할 것이다. 왜냐하면 어떤 라이브러리들은 하나 이상의 groupId를 가지는 것을 종종 찾게되기 때문이다. 이 도구를 통해 Maven 레파지토를 이해할 수 있을 것이다.
4.6 간단한 날씨 소스 코드
단순한 날씨 명령행 어플리케이션은 다음의 5개의 자바 클래스들로 구성된다.
org.sonatype.mavenbook.weather.Main
Maven 클래스는 정적 main() 함수를 가지며, 이 시스템에 대한 진입점이다.
Maven 클래스는 정적 main() 함수를 가지며, 이 시스템에 대한 진입점이다.
org.sonatype.mavenbook.weather.Weather
Weather 클래스는 온도나 습도와 같은 날씨 정보에 대한 위치와 주요 사실들을 포함하는 직관적인 자바 빈이다.
Weather 클래스는 온도나 습도와 같은 날씨 정보에 대한 위치와 주요 사실들을 포함하는 직관적인 자바 빈이다.
org.sonatype.mavenbook.weather.YahooRetriever
YahooRetriever 클래스는 야후 날씨에 접속해서 피드로부터 데이터의 InputStream 을 반환한다.
YahooRetriever 클래스는 야후 날씨에 접속해서 피드로부터 데이터의 InputStream 을 반환한다.
org.sonatype.mavenbook.weather.YahooParser
YahooParser 클래스는 야후 날씨로부터 XML을 파싱하고, Weather 객체를 반환한다.
YahooParser 클래스는 야후 날씨로부터 XML을 파싱하고, Weather 객체를 반환한다.
org.sonatype.mavenbook.weather.WeatherFormatter
WeatherFormatter 클래스는 Weather 객체를 받아들여서, VelocityContext 를 생성하고, Velocity 템플릿을 적용한다.
WeatherFormatter 클래스는 Weather 객체를 받아들여서, VelocityContext 를 생성하고, Velocity 템플릿을 적용한다.
여기에서 코드를 자세하게 설명하지는 않지만, 예제를 실행하는데 필요한 모든 코드를 제시할 것이다. 대부분의 독자들은 이 책에 있는 예제를 다운로드 받는다고 가정하지만, 이 장의 예제를 단계별로 따라하기 원할 수도 있는 독자들을 역시 염두한 것이다. 다음의 절은 simple-weather 프로젝트의 클래스들을 열거한다. 각각의 클래스들은 org.sonatype.mavenbook.weather 인 동일한 패키지에 위치해야 한다.
archetype:create로 생성된 App 와 AppTest 클래스를 지우고 새로운 패키지를 추가한다. 메이븐 프로젝트에서 모든 프로젝트 소스 코드는 src/main/java 에 저장된다. 새로운 프로젝트의 기존 디렉토리로부터 다음의 명령을 실행한다.
$ cd src/test/java/org/sonatype/mavenbook
$ rm AppTest.java
$ cd ../../../../../..
$ cd src/main/java/org/sonatype/mavenbook
$ rm App.java
$ mkdir weather
$ cd weather
$ rm AppTest.java
$ cd ../../../../../..
$ cd src/main/java/org/sonatype/mavenbook
$ rm App.java
$ mkdir weather
$ cd weather
이는 org.sonatype.mavenbook.weather 라는 새로운 패키지를 생성한다. 이제 이 디렉토리에 클래스들을 넣을 필요가 있다. 선호하는 텍스트 편집기를 사용해서 예제 4-4에 나타난 내용이 있는 Weather.java 라는 새로운 파일을 생성한다.
예제 4-4. simple-weather의 Weather 모델 객체
package org.sonatype.mavenbook.weather;
public class Weather {
private String city;
private String region;
private String country;
private String condition;
private String temp;
private String chill;
private String humidity;
public Weather() {}
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public String getCondition() { return condition; }
public void setCondition(String condition) { this.condition = condition; }
public String getTemp() { return temp; }
public void setTemp(String temp) { this.temp = temp; }
public String getChill() { return chill; }
public void setChill(String chill) { this.chill = chill; }
public String getHumidity() { return humidity; }
public void setHumidity(String humidity) { this.humidity = humidity; }
}
public class Weather {
private String city;
private String region;
private String country;
private String condition;
private String temp;
private String chill;
private String humidity;
public Weather() {}
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public String getCondition() { return condition; }
public void setCondition(String condition) { this.condition = condition; }
public String getTemp() { return temp; }
public void setTemp(String temp) { this.temp = temp; }
public String getChill() { return chill; }
public void setChill(String chill) { this.chill = chill; }
public String getHumidity() { return humidity; }
public void setHumidity(String humidity) { this.humidity = humidity; }
}
Weather 클래스는 야후 날씨 피드로부터 파싱된 날씨 정보를 담는데 사용되는 단순한 빈을 정의한다. 이 피드는 일출/일몰 시간에서부터 풍속/풍향까지 많은 정보를 제공한다. 이 예제를 가능한 한 단순하게 하기 위해서, Weather 모델 객체는 현재 상태의 온도, 체감 온도, 습도, 문자 설명만을 유지한다.
이제 동일한 디렉토리에 Main.java 라는 파일을 생성한다. 이 Main 클래스는 이 예제의 진입점인 정적 main() 함수를 포함한다. 예제 4-5를 참조하라.
예제 4-5. simple-weather의 Main 클래스
package org.sonatype.mavenbook.weather;
import java.io.InputStream;
import org.apache.log4j.PropertyConfigurator;
public class Main {
public static void main(String[] args) throws Exception {
// Configure Log4J
PropertyConfigurator.configure(Main.class.getClassLoader()
.getResource("log4j.properties"));
// Read the Zip Code from the Command-line (if none supplied, use 60202)
String zipcode = "60202";
try {
zipcode = args[0]);
} catch( Exception e ) {}
// Start the program
new Main(zipcode).start();
}
private String zip;
public Main(String zip) {
this.zip = zip;
}
public void start() throws Exception {
// Retrieve Data
InputStream dataIn = new YahooRetriever().retrieve( zip );
// Parse Data
Weather weather = new YahooParser().parse( dataIn );
// Format (Print) Data
System.out.print( new WeatherFormatter().format( weather ) );
}
}
import java.io.InputStream;
import org.apache.log4j.PropertyConfigurator;
public class Main {
public static void main(String[] args) throws Exception {
// Configure Log4J
PropertyConfigurator.configure(Main.class.getClassLoader()
.getResource("log4j.properties"));
// Read the Zip Code from the Command-line (if none supplied, use 60202)
String zipcode = "60202";
try {
zipcode = args[0]);
} catch( Exception e ) {}
// Start the program
new Main(zipcode).start();
}
private String zip;
public Main(String zip) {
this.zip = zip;
}
public void start() throws Exception {
// Retrieve Data
InputStream dataIn = new YahooRetriever().retrieve( zip );
// Parse Data
Weather weather = new YahooParser().parse( dataIn );
// Format (Print) Data
System.out.print( new WeatherFormatter().format( weather ) );
}
}
이 예제에 나타나는 main() 함수는 클래스패스로부터 리소스를 가지고 와서 Log4J를 설정한다. 그 다음에 명령행에서 우편번호를 읽으려고 한다. 우편번호를 읽으려고 하는 도중에 예외가 발생하면, 프로그램은 자동으로 60202 라는 우편번호를 세팅한다. 일단 우편번호를 읽으면, 메이븐의 인스턴스를 초기화하고 Main 인스턴스에 있는 start() 메소드를 호출한다. start() 메소드는 날씨 XML을 가지고 오기 위해 YahooRetriever 를 호출한다. YahooRetriever 는 InputStream 를 반환하고, 그 다음에 YahooParser 에게 넘긴다. YahooParser 는 야후 날씨 XML을 파싱하고 Weather 객체를 반환한다. 마지막으로 WeatherFormatter 은 Weather 객체를 가지고 표준 출력에 출력하는 String 형태로 반환한다.
동일한 디렉토리에 예제 4-6에 나타난 내용을 포함하는 YahooRetriever.java 라는 파일을 생성한다.
예제 4-6. simple-weather의 YahooRetriever 클래스
package org.sonatype.mavenbook.weather;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.apache.log4j.Logger;
public class YahooRetriever {
private static Logger log = Logger.getLogger(YahooRetriever.class);
public InputStream retrieve(int zipcode) throws Exception {
log.info( "Retrieving Weather Data" );
String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode;
URLConnection conn = new URL(url).openConnection();
return conn.getInputStream();
}
}
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.apache.log4j.Logger;
public class YahooRetriever {
private static Logger log = Logger.getLogger(YahooRetriever.class);
public InputStream retrieve(int zipcode) throws Exception {
log.info( "Retrieving Weather Data" );
String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode;
URLConnection conn = new URL(url).openConnection();
return conn.getInputStream();
}
}
이 단순한 클래스는 야후 날씨 API로 URLConnection 을 열고 InputStream 을 반환한다. 이 피드를 파싱하는 것을 만들기 위해 동일한 디렉토리에 YahooParser.java 파일을 생성할 필요가 있다. 예제 4-7을 참고하라.
예제 4-7. simple-weather 의 YahooParser 클래스
package org.sonatype.mavenbook.weather;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.io.SAXReader;
public class YahooParser {
private static Logger log = Logger.getLogger(YahooParser.class);
public Weather parse(InputStream inputStream) throws Exception {
Weather weather = new Weather();
log.info( "Creating XML Reader" );
SAXReader xmlReader = createXmlReader();
Document doc = xmlReader.read( inputStream );
log.info( "Parsing XML Response" );
weather.setCity( doc.valueOf("/rss/channel/y:location/@city") );
weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") );
weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") );
weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") );
weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") );
weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") );
weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") );
return weather;
}
private SAXReader createXmlReader() {
Map<String,String> uris = new HashMap<String,String>();
uris.put( "y", "http://xml.weather.yahoo.com/ns/rss/1.0" );
DocumentFactory factory = new DocumentFactory();
factory.setXPathNamespaceURIs( uris );
SAXReader xmlReader = new SAXReader();
xmlReader.setDocumentFactory( factory );
return xmlReader;
}
}
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.io.SAXReader;
public class YahooParser {
private static Logger log = Logger.getLogger(YahooParser.class);
public Weather parse(InputStream inputStream) throws Exception {
Weather weather = new Weather();
log.info( "Creating XML Reader" );
SAXReader xmlReader = createXmlReader();
Document doc = xmlReader.read( inputStream );
log.info( "Parsing XML Response" );
weather.setCity( doc.valueOf("/rss/channel/y:location/@city") );
weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") );
weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") );
weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") );
weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") );
weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") );
weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") );
return weather;
}
private SAXReader createXmlReader() {
Map<String,String> uris = new HashMap<String,String>();
uris.put( "y", "http://xml.weather.yahoo.com/ns/rss/1.0" );
DocumentFactory factory = new DocumentFactory();
factory.setXPathNamespaceURIs( uris );
SAXReader xmlReader = new SAXReader();
xmlReader.setDocumentFactory( factory );
return xmlReader;
}
}
YahooParser 는 이 예제에서 가장 복잡한 클래스이다. 여기에서 Dom4J나 Jaxen에 대해 세부사항을 다루지 않을 것이지만, 클래스에 대해 설명할 가치가 있다. YahooParser의 parse() 메소드는 InputStream 을 입력받아 Weather 객체를 반환한다. 이렇게 하기 위해서 Dom4J를 사용해서 XML 문서를 파싱할 필요가 있다. 야후 날씨 XML 네임스페이스의 요소에 관심이 있기 때문에 네임스페이스를 인식하는 SAXReader 를 createXmlReader() 메소드에 만들 필요가 있다. 일단 이 메소드를 생성하고 문서를 파싱한다면, org.dom4j.Document 를 다시 받게 된다. 하위 요소를 통해 순환하는 대신에 XPath 표현을 사용해서 필요한 각각의 정보를 단순하게 뽑아낸다. Dom4J는 이 예제에서 XML 파싱을 제공하며, Jaxen은 XPath 기능을 제공한다.
일단 Weather 객체를 만들었다면, 사람이 읽을 수 있는 결과물로 형식을 바꿀 필요가 있다. 다른 클래스와 같이 동일한 디렉토리에 WeatherFormatter.java 라고 하는 파일을 생성한다. 예제 4-8을 참조하라.
예제 4-8. simple-weather의 WeatherFormatter 클래스
package org.sonatype.mavenbook.weather;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import org.apache.log4j.Logger;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
public class WeatherFormatter {
private static Logger log = Logger.getLogger(WeatherFormatter.class);
public String format( Weather weather ) throws Exception {
log.info( "Formatting Weather Data" );
Reader reader =
new InputStreamReader( getClass().getClassLoader()
.getResourceAsStream("output.vm"));
VelocityContext context = new VelocityContext();
context.put("weather", weather );
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "", reader);
return writer.toString();
}
}
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import org.apache.log4j.Logger;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
public class WeatherFormatter {
private static Logger log = Logger.getLogger(WeatherFormatter.class);
public String format( Weather weather ) throws Exception {
log.info( "Formatting Weather Data" );
Reader reader =
new InputStreamReader( getClass().getClassLoader()
.getResourceAsStream("output.vm"));
VelocityContext context = new VelocityContext();
context.put("weather", weather );
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "", reader);
return writer.toString();
}
}
WeatherFormatter 는 템플릿을 적용하기 위해 Velocity를 사용한다. format() 메소드는 Weather 빈을 입력받아 특정 형태의 String 으로 반환한다. format()가 수행하는 첫번째 일은 클래스패스로부터 output.vm 이라고 하는 Velocity 템플릿을 로딩한다. 그 다음에 weather 라고 하는 단일 Weather 객체를 가지는 VelocityContext 를 생성한다. StringWriter 는 템플릿 병합 결과를 담는데 생성된다. 템플릿은 그 다음에 Velocity.evaluate()를 호출하면서 수행되고, 결과가 String 으로 반환된다.
이 예제를 실행하기 전에 클래스패스에 몇가지 리소스를 추가할 필요가 있다.
4.7 리소스 추가
위의 프로젝트는 log4j.properties 라는 이름의 클래스패스 리소스를 갖는 Log4J를 설정하는 Main 클래스와, output.vm 라는 이름의 클래스패스로부터 Velocity 템플릿을 참고하는 WeatherFormatter 의 두개의 클래스패스 리소스에 의존관계를 가진다. 이 두가지 리소스는 기본 패키에 위치할 필요가 있다. (혹은 클래스의 루트)
이 리소스를 추가하려면, 프로젝트의 기본 디렉토리로부터 새로운 디렉토리인 src/main/resources 를 생성할 필요가 있다. 이 디렉토리는 archetype:create 작업으로 생성되지 않기 때문에 프로젝트의 기본 디렉토리로부터 다음의 명령을 실행함으로써 생성할 필요가 있다.
$ cd src/main
$ mkdir resources
$ cd resources
$ mkdir resources
$ cd resources
일단 리소스 디렉토리가 생성되면, 두개의 리소스를 추가할 수 있다. 먼저 예제 4-9에 나타난 것과 같이 resources 디렉토리에 log4j.properties 파일을 추가한다.
예제 4-9. simple-weather의 Log4J 설정 파일
# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=INFO
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n
log4j.rootCategory=INFO, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=INFO
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n
log4j.properties 파일은 PatternLayout을 사용해서 표준 출력에 모든 로그 메시지를 출력하는 Log4J를 단순하게 설정한다. 마지막으로 이 명령행 프로그램의 결과를 조회하는데 사용되는 Velocity 템플릿인 output.vm 를 생성할 필요가 있다. resources/ 디렉토리에 output.vm 를 생성한다. 예제 4-10을 참고하라.
예제 4-10. simple-weather의 Velocity 템플릿 결과
*********************************
Current Weather Conditions for:
${weather.city}, ${weather.region}, ${weather.country}
Temperature: ${weather.temp}
Condition: ${weather.condition}
Humidity: ${weather.humidity}
Wind Chill: ${weather.chill}
*********************************
Current Weather Conditions for:
${weather.city}, ${weather.region}, ${weather.country}
Temperature: ${weather.temp}
Condition: ${weather.condition}
Humidity: ${weather.humidity}
Wind Chill: ${weather.chill}
*********************************
위의 템플릿은 WeatherFormatter 으로 넘기는 Weather 빈인 weather라는 변수에 대한 수많은 참조를 포함한다. ${weather.temp} 는 temp 빈 속성에 대한 값을 가지고 보여주는 표시이다. 이제 적절한 지점에 모든 프로젝트 코드를 가지기 때문에 예제를 실행하기 위해 메이븐을 사용할 수 있다.
4.8 간단한 날씨 프로그램 실행
Codehaus Mojo project 로부터 Exec 플러그인을 사용해서 위의 프로그램을 실행할 수 있다. Main 클래스를 실행하려면, 프로젝트의 기본 디렉토리에서 다음의 명령을 실행한다.
$ mvn install
$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main
...
[INFO] [exec:java]
0 INFO YahooRetriever - Retrieving Weather Data
134 INFO YahooParser - Creating XML Reader
333 INFO YahooParser - Parsing XML Response
420 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
Evanston, IL, US
Temperature: 45
Condition: Cloudy
Humidity: 76
Wind Chill: 38
*********************************
...
$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main
...
[INFO] [exec:java]
0 INFO YahooRetriever - Retrieving Weather Data
134 INFO YahooParser - Creating XML Reader
333 INFO YahooParser - Parsing XML Response
420 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
Evanston, IL, US
Temperature: 45
Condition: Cloudy
Humidity: 76
Wind Chill: 38
*********************************
...
Main 클래스에 명령행 인자를 입력하지 않았기 때문에, 기본 우편번호인 60202로 사용되었다. 우편번호를 입력하려면, -Dexec.args 인자를 사용해서 다음과 같이 우편번호를 넘길 수 있다.
$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main \
-Dexec.args="70112"
...
[INFO] [exec:java]
0 INFO YahooRetriever - Retrieving Weather Data
134 INFO YahooParser - Creating XML Reader
333 INFO YahooParser - Parsing XML Response
420 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
New Orleans, LA, US
Temperature: 82
Condition: Fair
Humidity: 71
Wind Chill: 82
*********************************
[INFO] Finished at: Sun Aug 31 09:33:34 CDT 2008
...
-Dexec.args="70112"
...
[INFO] [exec:java]
0 INFO YahooRetriever - Retrieving Weather Data
134 INFO YahooParser - Creating XML Reader
333 INFO YahooParser - Parsing XML Response
420 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
New Orleans, LA, US
Temperature: 82
Condition: Fair
Humidity: 71
Wind Chill: 82
*********************************
[INFO] Finished at: Sun Aug 31 09:33:34 CDT 2008
...
보는 바와 같이 야후 날씨에서 데이터를 추출하고, 결과를 파싱하고, Velocity를 통해 결과 데이터를 출력하는 간단한 날씨 명령행 도구를 성공적으로 실행했다. 이러한 모든 것을 프로젝트의 소스 코드에 많은 것을 수행하지 않고 pom.xml에 최소한의 설정을 추가함으로써 수행했다. 어떠한 "빌드 절차"가 관여되지 않았음을 유의하라. Java 컴파일러가 어디에서 소스를 바이트코드로 컴파일하거나 어떻게 하는지를 정의할 필요가 없었으며, 예제 어플리케이션을 실행했을 때 바이트코드가 어떻게 위치해야하는지를 빌드 시스템에 지시할 필요가 없었다. 몇가지 의존관계만을 포함함으로써 적절한 메이븐 위상을 위치시키는 작업을 했을 뿐이다.
4.8.1. Maven Exec 플러그인
Exec 플러그인은 자바 클래스들과 다른 스크립트를 실행 가능하게 한다. 핵심 메이븐 플러그인은 아니지만, Codehaus에서 관리하는 Mojo 프로젝트에서 사용 가능하다. Exec 플러그인에 대한 전체 설명을 위해 다음을 실행한다.
$ mvn help:describe -Dplugin=exec -Dfull
이는 Maven Exec 플러그인에서 사용 가능한 모든 goal들을 나타낸다. Help 플러그인은 또한 Exec 플러그인에 대한 유효한 파라미터들을 모두 열거한다. 만일 명령행 인자에 넘기기 위해 Exec 플러그인의 행위를 변경하려면, 개발 동안 테스트를 실행하는 외부에서 어플리케이션을 실행하는 방식으로 의존해서는 안된다. 좀 더 강력한 해결책으로 이장 이후에 나오는 4.13 절에 설명된 Maven Assembly 플러그인을 사용한다.
4.8.2. 프로젝트 의존관계 살펴보기
Exec 플러그인은 클래스패스에 적합한 의존관계를 로딩할 필요없이 가장 간단한 날씨 프로그램을 실행도록 해준다. 다른 빌드 시스템에서 모든 프로그램 의존관계들을 JAR 파일의 집합체를 포함하는 lib/ 디렉토리와 같은 것에 복사해야 할 것이다. 그 다음에 클래스패스에 프로그램의 바이트코드와 모든 의존관계가 포함되는 간단한 스크립트를 작성해야만 할 것이다. 그 다음에 java org.sonatype.mavenbook.weather.Main을 실행할 수 있을 것이다. Exec 플러그인은 메이븐이 이미 클래스패스와 의존관계를 어떻게 생성하고 관리하는지를 알고 있다는 사실을 염두해둔다.
이는 편리하지만, 프로젝트의 클래스패스에 무엇이 포함되는지를 정확하게 아는 것 역시 좋다. 프로젝트가 Dom4J, Log4J, Jaxen, Velocity와 같은 몇가지 라이브러리들에 대해 의존관계를 갖지만, 몇가지 연결 의존관계 역시 의존하고 있다. 만일 클래스패스에 어떤 것들이 있는지를 알 필요가 있다면, 연결된 의존관계의 목록을 출력하는 Maven Dependency 플러그인을 사용할 수 있다. 간단한 날씨 프로젝트에 대해 이 목록을 출력하려면, 다음과 같이 dependency:resolve goal을 실행한다.
$ mvn dependency:resolve
...
[INFO] [dependency:resolve]
[INFO]
[INFO] The following files have been resolved:
[INFO] com.ibm.icu:icu4j:jar:2.6.1 (scope = compile)
[INFO] commons-collections:commons-collections:jar:3.1 (scope = compile)
[INFO] commons-lang:commons-lang:jar:2.1 (scope = compile)
[INFO] dom4j:dom4j:jar:1.6.1 (scope = compile)
[INFO] jaxen:jaxen:jar:1.1.1 (scope = compile)
[INFO] jdom:jdom:jar:1.0 (scope = compile)
[INFO] junit:junit:jar:3.8.1 (scope = test)
[INFO] log4j:log4j:jar:1.2.14 (scope = compile)
[INFO] oro:oro:jar:2.0.8 (scope = compile)
[INFO] velocity:velocity:jar:1.5 (scope = compile)
[INFO] xalan:xalan:jar:2.6.0 (scope = compile)
[INFO] xerces:xercesImpl:jar:2.6.2 (scope = compile)
[INFO] xerces:xmlParserAPIs:jar:2.6.2 (scope = compile)
[INFO] xml-apis:xml-apis:jar:1.0.b2 (scope = compile)
[INFO] xom:xom:jar:1.0 (scope = compile)
...
[INFO] [dependency:resolve]
[INFO]
[INFO] The following files have been resolved:
[INFO] com.ibm.icu:icu4j:jar:2.6.1 (scope = compile)
[INFO] commons-collections:commons-collections:jar:3.1 (scope = compile)
[INFO] commons-lang:commons-lang:jar:2.1 (scope = compile)
[INFO] dom4j:dom4j:jar:1.6.1 (scope = compile)
[INFO] jaxen:jaxen:jar:1.1.1 (scope = compile)
[INFO] jdom:jdom:jar:1.0 (scope = compile)
[INFO] junit:junit:jar:3.8.1 (scope = test)
[INFO] log4j:log4j:jar:1.2.14 (scope = compile)
[INFO] oro:oro:jar:2.0.8 (scope = compile)
[INFO] velocity:velocity:jar:1.5 (scope = compile)
[INFO] xalan:xalan:jar:2.6.0 (scope = compile)
[INFO] xerces:xercesImpl:jar:2.6.2 (scope = compile)
[INFO] xerces:xmlParserAPIs:jar:2.6.2 (scope = compile)
[INFO] xml-apis:xml-apis:jar:1.0.b2 (scope = compile)
[INFO] xom:xom:jar:1.0 (scope = compile)
보는 바와 같이 프로젝트는 매우 많은 의존관계를 갖는다. 단지 4개의 라이브러리에만 직접 의존관계를 포함시켰지만, 총 15개의 의존관계가 나타나는 것을 볼 수 있다. Dom4J의 경우 Xerces와 XML Parser API에 대해 의존관계를 갖는 반면에, Jaxen은 클래스패스에서 사용 가능한 Xalan에 대해 의존관계를 갖는다. Dependency 플러그인은 프로젝트가 컴파일 되는 환경하에서 최종 의존관계에 대한 내용을 보여준다. 프로젝트에 대한 전체 의존관계 트리에 대해서 알고자 한다면, 다음과 같이 dependency:tree goal을 실행할 수 있다.
$ mvn dependency:tree
...
[INFO] [dependency:tree]
[INFO] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0
[INFO] +- log4j:log4j:jar:1.2.14:compile
[INFO] +- dom4j:dom4j:jar:1.6.1:compile
[INFO] | \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] +- jaxen:jaxen:jar:1.1.1:compile
[INFO] | +- jdom:jdom:jar:1.0:compile
[INFO] | +- xerces:xercesImpl:jar:2.6.2:compile
[INFO] | \- xom:xom:jar:1.0:compile
[INFO] | +- xerces:xmlParserAPIs:jar:2.6.2:compile
[INFO] | +- xalan:xalan:jar:2.6.0:compile
[INFO] | \- com.ibm.icu:icu4j:jar:2.6.1:compile
[INFO] +- velocity:velocity:jar:1.5:compile
[INFO] | +- commons-collections:commons-collections:jar:3.1:compile
[INFO] | +- commons-lang:commons-lang:jar:2.1:compile
[INFO] | \- oro:oro:jar:2.0.8:compile
[INFO] +- org.apache.commons:commons-io:jar:1.3.2:test
[INFO] \- junit:junit:jar:3.8.1:test
...
[INFO] [dependency:tree]
[INFO] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0
[INFO] +- log4j:log4j:jar:1.2.14:compile
[INFO] +- dom4j:dom4j:jar:1.6.1:compile
[INFO] | \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] +- jaxen:jaxen:jar:1.1.1:compile
[INFO] | +- jdom:jdom:jar:1.0:compile
[INFO] | +- xerces:xercesImpl:jar:2.6.2:compile
[INFO] | \- xom:xom:jar:1.0:compile
[INFO] | +- xerces:xmlParserAPIs:jar:2.6.2:compile
[INFO] | +- xalan:xalan:jar:2.6.0:compile
[INFO] | \- com.ibm.icu:icu4j:jar:2.6.1:compile
[INFO] +- velocity:velocity:jar:1.5:compile
[INFO] | +- commons-collections:commons-collections:jar:3.1:compile
[INFO] | +- commons-lang:commons-lang:jar:2.1:compile
[INFO] | \- oro:oro:jar:2.0.8:compile
[INFO] +- org.apache.commons:commons-io:jar:1.3.2:test
[INFO] \- junit:junit:jar:3.8.1:test
충돌이나 다른 이유로 인해 제거되는 산출물들을 포함하여 진정으로 전체 의존관계 추적을 보기를 원한다면, 다음과 같은 debug 플래그를 사용해서 메이븐을 실행한다.
$ mvn install -X
...
[DEBUG] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 (selected for null)
[DEBUG] log4j:log4j:jar:1.2.14:compile (selected for compile)
[DEBUG] dom4j:dom4j:jar:1.6.1:compile (selected for compile)
[DEBUG] xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile)
[DEBUG] jaxen:jaxen:jar:1.1.1:compile (selected for compile)
[DEBUG] jaxen:jaxen:jar:1.1-beta-6:compile (removed - causes a cycle
in the graph)
[DEBUG] jaxen:jaxen:jar:1.0-FCS:compile (removed - causes a cycle in
the graph)
[DEBUG] jdom:jdom:jar:1.0:compile (selected for compile)
[DEBUG] xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer found:
1.0.b2)
[DEBUG] xerces:xercesImpl:jar:2.6.2:compile (selected for compile)
[DEBUG] xom:xom:jar:1.0:compile (selected for compile)
[DEBUG] xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile)
[DEBUG] xalan:xalan:jar:2.6.0:compile (selected for compile)
[DEBUG] xml-apis:xml-apis:1.0.b2.
[DEBUG] com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile)
[DEBUG] velocity:velocity:jar:1.5:compile (selected for compile)
[DEBUG] commons-collections:commons-collections:jar:3.1:compile
(selected for compile)
[DEBUG] commons-lang:commons-lang:jar:2.1:compile (selected for compile)
[DEBUG] oro:oro:jar:2.0.8:compile (selected for compile)
[DEBUG] junit:junit:jar:3.8.1:test (selected for test)
...
...
[DEBUG] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 (selected for null)
[DEBUG] log4j:log4j:jar:1.2.14:compile (selected for compile)
[DEBUG] dom4j:dom4j:jar:1.6.1:compile (selected for compile)
[DEBUG] xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile)
[DEBUG] jaxen:jaxen:jar:1.1.1:compile (selected for compile)
[DEBUG] jaxen:jaxen:jar:1.1-beta-6:compile (removed - causes a cycle
in the graph)
[DEBUG] jaxen:jaxen:jar:1.0-FCS:compile (removed - causes a cycle in
the graph)
[DEBUG] jdom:jdom:jar:1.0:compile (selected for compile)
[DEBUG] xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer found:
1.0.b2)
[DEBUG] xerces:xercesImpl:jar:2.6.2:compile (selected for compile)
[DEBUG] xom:xom:jar:1.0:compile (selected for compile)
[DEBUG] xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile)
[DEBUG] xalan:xalan:jar:2.6.0:compile (selected for compile)
[DEBUG] xml-apis:xml-apis:1.0.b2.
[DEBUG] com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile)
[DEBUG] velocity:velocity:jar:1.5:compile (selected for compile)
[DEBUG] commons-collections:commons-collections:jar:3.1:compile
(selected for compile)
[DEBUG] commons-lang:commons-lang:jar:2.1:compile (selected for compile)
[DEBUG] oro:oro:jar:2.0.8:compile (selected for compile)
[DEBUG] junit:junit:jar:3.8.1:test (selected for test)
...
debug 출력에서 작동하는 의존관계 관리 시스템의 내부를 보게 된다. 여기서 보는 것은 해당 프로젝트에 대한 의존관계 트리이다. 메이븐은 해당 프로젝트의 의존관계 전체와 그 의존관계들의 의존관계들에 대해서(그리고 의존관계의 의존관계에 대한 의존관계들) 전체 메이븐 위상을 출력한다. simple-weather가 jaxen이 xom에 대해 의존관계를 갖고, 차례로 xom이 icu4j에 대해 의존관계를 갖는 것을 볼 수 있다. 또한 중복을 제거하고, 서로 다른 버전간의 충돌을 해결하는 의존관계 그래프를 생성하는 것을 볼 수 있다. 의존관계에 대해 문제가 있다면, dependency:resolve에 의해서 생성되는 목록 보다 점 더 깊게 파고들어가는 것이 종종 도움이 된다. debug를 사용함으로써 작동하는 메이븐의 의존관계 체계를 볼 수 있다.
4.9. 단위 테스트 작성
메이븐은 단위 테스트에 대한 내장 지원을 가지고 있으며, 테스팅은 기본 메이븐 생명주기 중의 일부분이다. 간단한 날씨 프로젝트에 몇가지 단위 테스트들을 추가해보자. 먼저 src/test/java 밑에 org.sonatype.mavenbook.weather 패키지를 생성한다.
$ cd src/test/java
$ cd org/sonatype/mavenbook
$ mkdir weather
$ cd weather
$ cd org/sonatype/mavenbook
$ mkdir weather
$ cd weather
이 시점에서 두개의 단위 테스트를 생성할 것이다. 첫번째는 YahooParser 를 테스트하며, 두번째는 WeatherFormatter 를 테스트한다. weather 패키지에서 예제 4-11에 나타나는 내용을 포함하는 YahooParserTest.java 라는 파일을 생성한다.
예제 4-11. simple-weather의 YahooParserTest 단위 테스트
package org.sonatype.mavenbook.weather.yahoo;
import java.io.InputStream;
import junit.framework.TestCase;
import org.sonatype.mavenbook.weather.Weather;
import org.sonatype.mavenbook.weather.YahooParser;
public class YahooParserTest extends TestCase {
public YahooParserTest(String name) {
super(name);
}
public void testParser() throws Exception {
InputStream nyData =
getClass().getClassLoader().getResourceAsStream("ny-weather.xml");
Weather weather = new YahooParser().parse( nyData );
assertEquals( "New York", weather.getCity() );
assertEquals( "NY", weather.getRegion() );
assertEquals( "US", weather.getCountry() );
assertEquals( "39", weather.getTemp() );
assertEquals( "Fair", weather.getCondition() );
assertEquals( "39", weather.getChill() );
assertEquals( "67", weather.getHumidity() );
}
}
import java.io.InputStream;
import junit.framework.TestCase;
import org.sonatype.mavenbook.weather.Weather;
import org.sonatype.mavenbook.weather.YahooParser;
public class YahooParserTest extends TestCase {
public YahooParserTest(String name) {
super(name);
}
public void testParser() throws Exception {
InputStream nyData =
getClass().getClassLoader().getResourceAsStream("ny-weather.xml");
Weather weather = new YahooParser().parse( nyData );
assertEquals( "New York", weather.getCity() );
assertEquals( "NY", weather.getRegion() );
assertEquals( "US", weather.getCountry() );
assertEquals( "39", weather.getTemp() );
assertEquals( "Fair", weather.getCondition() );
assertEquals( "39", weather.getChill() );
assertEquals( "67", weather.getHumidity() );
}
}
YahooParserTest 는 JUnit에 의해 정의된 TestCase 클래스를 확장한다. JUnit 테스트에 대한 일반적인 패턴으로, 상위클래스의 생성자를 호출하는 단일 String 인자를 가지는 생성자와, 단위 테스트로 호출되는 “test”로 시작하는 여러 public 메소드를 가진다. 알려진 값들로 XML 문서를 파싱함으로써 YahooParser 를 테스트하는 단일 테스트 메소드인 testParser를 정의한다. 테스트 XML 문서는 ny-weather.xml 라고 하고 클래스패스로부터 로딩한다. 4.11 절에서 테스트 리소스를 추가할 것이다. Maven 프로젝트의 디렉토리 구조에서 테스트 리소스를 포함하는 디렉토리에서 ny-weather.xml 파일을 찾는다. – 즉 ${basedir}/src/test/resources 밑의 org/sonatype/mavenbook/weather/yahoo/ny-weather.xml. 파일은 InputStream 으로 읽어들여서 YahooParser의 parse() 메소드로 넘겨진다. parse() 메소드는 Weather 객체를 반환하며, 그 다음에 TestCase에 의해 정외된 메소드인 assetEquals()을 여러 번호출하여 테스트된다.
동일한 디렉토리에서 WeatherFormatterTest.java라는 파일을 생성한다. 예제 4-12를 참고하라.
예제 4-12. simple-weather의 WeatherFormatterTest 단위 테스트
package org.sonatype.mavenbook.weather.yahoo;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.sonatype.mavenbook.weather.Weather;
import org.sonatype.mavenbook.weather.WeatherFormatter;
import org.sonatype.mavenbook.weather.YahooParser;
import junit.framework.TestCase;
public class WeatherFormatterTest extends TestCase {
public WeatherFormatterTest(String name) {
super(name);
}
public void testFormat() throws Exception {
InputStream nyData =
getClass().getClassLoader().getResourceAsStream("ny-weather.xml");
Weather weather = new YahooParser().parse( nyData );
String formattedResult = new WeatherFormatter().format( weather );
InputStream expected =
getClass().getClassLoader().getResourceAsStream("format-expected.dat");
assertEquals( IOUtils.toString( expected ).trim(),
formattedResult.trim() );
}
}
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.sonatype.mavenbook.weather.Weather;
import org.sonatype.mavenbook.weather.WeatherFormatter;
import org.sonatype.mavenbook.weather.YahooParser;
import junit.framework.TestCase;
public class WeatherFormatterTest extends TestCase {
public WeatherFormatterTest(String name) {
super(name);
}
public void testFormat() throws Exception {
InputStream nyData =
getClass().getClassLoader().getResourceAsStream("ny-weather.xml");
Weather weather = new YahooParser().parse( nyData );
String formattedResult = new WeatherFormatter().format( weather );
InputStream expected =
getClass().getClassLoader().getResourceAsStream("format-expected.dat");
assertEquals( IOUtils.toString( expected ).trim(),
formattedResult.trim() );
}
}
이 간단한 프로젝트에서 두번째 단위 테스트는 WeatherFormatter을 테스트하는 것이다. YahooParserTest와 같이 WeatherFormatterTest 역시 JUnit의 TestCase 클래스를 상속받는다. 단일 테스트 함수는 단위 테스트의 클래스패스를 통해 ${basedir}/src/test/resources 밑의 org/sonatype/mavenbook/weather/yahoo 디렉토리로부터 동일한 테스트 리소스를 읽는다. 이 장의 이후의 4.11 절에서 테스트 리소스를 추가할 것이다. WeatherFormatterTest 은 Weather 객체를 나누는 YahooParser를 통해 이 예제 입력 파일을 실행하고, 그 다음에 객체는 WeatherFormatter 으로 형태가 변경된다. WeatherFormatter은 String을 출력하기 때문에, 예상되는 입력물에 대해 테스트할 필요가 있다. 예상되는 입력은 ny-weather.xml과 같은 디렉토리에 위치한 format-expected.dat 라는 텍스트 파일에 담겨진다. 테스트의 결과물을 예상 결과와 비교하려면, InputStream 으로 이 예상 결과를 읽어들여서 이 파일을 String 으로 변환하기 위해 Apache Commons IO의 IOUtils 클래스를 사용한다. String은 그 다음에 assertEquals()를 사용해서 테스트 출력과 비교된다.
4.10. 테스트 영역의 의존관계 추가
WeatherFormatterTest에서 Apache Commons IO로부터 IOUtils 클래스인 유틸리티를 사용했다. – IOUtils는 입력/출력 오퍼레이션으로부터의 대부분의 작업을 수행하는 수많은 도움이 되는 정적 함수를 제공한다. 위의 특정 단위 테스트에서 String 으로 format-expected.dat 클래스패스 리소를 복사하기 위해 IOUtils.toString()을 사용했다. 이를 Commons IO를 사용하지 않고 수행했지만, 다양한 InputStreamReader와 StringWriter 객체를 가지고 처리하려면 추가로 6~7 줄의 코드를 필요로 한다. Commons IO를 사용했던 주요 이유가 Commons IO에 대한 테스트 영역의 의존관계를 추가하려는 것이다.
테스트 영역의 의존관계는 테스트 컴파일과 실행에서만 클래스패스 상으로 사용 가능한 의존관계이다. 프로젝트가 war나 ear 패키징을 가지고 있다면, test 영역의 의존관계는 프로젝트의 결과물에 포함되지 않는다. test 영역의 의존관계를 추가하려면, 예제 4-13에 나타난 바와 같이 프로젝트의 <dependencies> 에 <dependency> 요소를 추가한다.
예제 4-13. test 영역 의존관계 추가
<project>
...
<dependencies>
...
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
<scope>test</scope>
</dependency>
...
</dependencies>
</project>
...
<dependencies>
...
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
<scope>test</scope>
</dependency>
...
</dependencies>
</project>
pom.xml에 위의 의존관계를 추가한 후에, mvn dependency:resolve를 실행하면 test 영역으로 의존관계로 포함된 commons-io를 볼 수 있을 것이다. 프로젝트의 단위 테스트를 실행기 전에 한가지 더 해야할 것이 있다. 이 단위 테스트가 의존하고 있는 클래스패스 리소스를 추가하는 것이다. 의존관계 영역은 9장의 9.4.1절에 상세하게 소개한다.
4.11. 단위 테스트 리소스 추가
단위 테스트는 테스트에 지정된 여러 리소스들에 대한 접근을 가진다. 종종 예상 결과를 포함하는 파일들과 테스트 클래스패스에 임의의 입력을 포함하는 파일들을 저장할 것이다. 위의 프로젝트에서 YahooParserTest 에 대한 테스트 XML 문서로 ny-weather.xml 라는 파일과 format-expected.dat 에 WeatherFormatter로 부터의 예상 출력을 포함하는 파일을 저장한다.
테스트 리소스를 추가하려면, src/test/resources 디렉토리를 생성할 필요가 있다. 이는 Maven 이 단위 테스트 리소스를 찾는 기본 디렉토리이다. 이 디렉토리를 생성하려면, 프로젝트의 기본 디렉토리에서 다음의 명령을 실행한다.
$ cd src/test
$ mkdir resources
$ cd resources
$ mkdir resources
$ cd resources
일단 resources/ 디렉토리를 생성했다면, 거기서 format-expected.dat라는 파일을 생성한다. 예제 4-14를 참고하라.
예제 4-14. simple-weather의 WeatherFormatterTest 의 예상된 결과
*********************************
Current Weather Conditions for:
New York, NY, US
Temperature: 39
Condition: Fair
Humidity: 67
Wind Chill: 39
*********************************
Current Weather Conditions for:
New York, NY, US
Temperature: 39
Condition: Fair
Humidity: 67
Wind Chill: 39
*********************************
위의 파일은 유사하게 보여야 한다. Maven Exec 플러그인을 사용해서 간단한 날씨 프로젝트를 실행했을 때 이전에 생성되었던 결과와 동일하다. 리소스 디렉토리에 추가할 두번째 파일은 ny-weather.xml이다. 예제 4-15를 참고하라.
예제 4-15. simple-weather의 YahooParserTest XML 입력
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
<channel>
<title>Yahoo! Weather - New York, NY</title>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link>
<description>Yahoo! Weather for New York, NY</description>
<language>en-us</language>
<lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate>
<ttl>60</ttl>
<yweather:location city="New York" region="NY" country="US" />
<yweather:units temperature="F" distance="mi" pressure="in" speed="mph" />
<yweather:wind chill="39" direction="0" speed="0" />
<yweather:atmosphere humidity="67" visibility="1609" pressure="30.18"
rising="1" />
<yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" />
<image>
<title>Yahoo! Weather</title>
<width>142</width>
<height>18</height>
<link>http://weather.yahoo.com/</link>
<url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url>
</image>
<item>
<title>Conditions for New York, NY at 8:51 pm EDT</title>
<geo:lat>40.67</geo:lat>
<geo:long>-73.94</geo:long>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/\</link>
<pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate>
<yweather:condition text="Fair" code="33" temp="39"
date="Sat, 10 Nov 2007 8:51 pm EDT" />
<description><![CDATA[
<img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br />
<b>Current Conditions:</b><br />
Fair, 39 F<BR /><BR />
<b>Forecast:</b><BR />
Sat - Partly Cloudy. High: 45 Low: 32<br />
Sun - Sunny. High: 50 Low: 38<br />
<br />
]]></description>
<yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45"
text="Partly Cloudy" code="29" />
<yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50"
text="Sunny" code="32" />
<guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid>
</item>
</channel>
</rss>
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
<channel>
<title>Yahoo! Weather - New York, NY</title>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link>
<description>Yahoo! Weather for New York, NY</description>
<language>en-us</language>
<lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate>
<ttl>60</ttl>
<yweather:location city="New York" region="NY" country="US" />
<yweather:units temperature="F" distance="mi" pressure="in" speed="mph" />
<yweather:wind chill="39" direction="0" speed="0" />
<yweather:atmosphere humidity="67" visibility="1609" pressure="30.18"
rising="1" />
<yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" />
<image>
<title>Yahoo! Weather</title>
<width>142</width>
<height>18</height>
<link>http://weather.yahoo.com/</link>
<url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url>
</image>
<item>
<title>Conditions for New York, NY at 8:51 pm EDT</title>
<geo:lat>40.67</geo:lat>
<geo:long>-73.94</geo:long>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/\</link>
<pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate>
<yweather:condition text="Fair" code="33" temp="39"
date="Sat, 10 Nov 2007 8:51 pm EDT" />
<description><![CDATA[
<img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br />
<b>Current Conditions:</b><br />
Fair, 39 F<BR /><BR />
<b>Forecast:</b><BR />
Sat - Partly Cloudy. High: 45 Low: 32<br />
Sun - Sunny. High: 50 Low: 38<br />
<br />
]]></description>
<yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45"
text="Partly Cloudy" code="29" />
<yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50"
text="Sunny" code="32" />
<guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid>
</item>
</channel>
</rss>
위의 파일은 YahooParserTest을 위한 테스트 XML 문서를 포함한다. 이 파일을 저장함으로써 야후 날씨로부터 XML 응답을 조회하지 않고 YahooParser를 테스트할 수 있다.
4.12. 단위 테스트 실행
이제 프로젝트가 단위 테스트가 갖추어졌으므로 이를 실행해보자. 단위 테스트를 실행하기 위해서 특별한 것을 할 필요가 없다. test 단계는 메이븐 생명주기의 일반적인 부분이다. mvn package나 mvn install을 실행할 때마다 Maven test를 실행한다. test 단계를 포함하여 모든 생명주기 단계를 실행하기 원하면, 다음과 같이 mvn test을 실행한다.
$ mvn test
...
[INFO] [surefire:test]
[INFO] Surefire report directory: ~/examples/simple-weather/target/\
surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest
0 INFO YahooParser - Creating XML Reader
177 INFO YahooParser - Parsing XML Response
239 INFO WeatherFormatter - Formatting Weather Data
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.547 sec
Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest
475 INFO YahooParser - Creating XML Reader
483 INFO YahooParser - Parsing XML Response
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] [surefire:test]
[INFO] Surefire report directory: ~/examples/simple-weather/target/\
surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest
0 INFO YahooParser - Creating XML Reader
177 INFO YahooParser - Parsing XML Response
239 INFO WeatherFormatter - Formatting Weather Data
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.547 sec
Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest
475 INFO YahooParser - Creating XML Reader
483 INFO YahooParser - Parsing XML Response
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
명령행에서 mvn test를 실행하는 것은 Maven이 test 단계까지 모든 생명주기 단계를 실행하도록 하는 것이다. Maven Surefire 플러그인은 test 단계에 포함된 test goal을 가지고 있다. 이 test goal은 해당 프로젝트가 src/test/java에서 찾을 수 있는 모든 단위 테스트를 실행한다. 이 프로젝트의 경우에, Surefire 플러그인의 test goal이 WeatherFormatterTest와 YahooParserTest를 실행하는 것을 볼 수 있다. Surefire 플러그인이 JUnit 테스트를 실행할 때 ${basedir}/target/surefire-reports 디렉토리에 XML과 테스트 리포트를 생성한다. 테스트가 실패하면 단위 테스트에 의해 생성된 스택 트레이스와 에러 메시지와 같은 세부사항에 대해 이 디렉토리를 살펴볼 수 있다.
4.12.1. 테스트 실패 무시
단위 테스트를 실패하는 시스템에 대한 개발을 종종 목격할 것이다. TDD (Test-Driven Development)를 적용하고 있다면, 테스트 실패를 프로젝트가 얼마나 완벽에 가까운지에 대한 측정으로 사용할 수도 있다. 실패한 단위 테스트를 가지고 있고, 그럼에도 불구하고 빌드 결과를 만들고자 한다면, 메이븐에게 빌드 실패를 무시하라고 전달할 필요가 있을 것이다. 메이븐이 빌드 실패를 만났을 때 기본 행위는 현재 빌드를 멈추는 것이다. Surefire 플러그인이 실패한 테스트 케이스를 만나더라도 프로젝트 빌드를 계속하려면, Surefire 플러그인의 testFailureIgnore 설정 속성에 true 세팅한다. 예제 4-16을 참고하라.
예제 4-16. 단위 테스트 실패 무시
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
플러그인 문서(http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html)는 예제 4-17과 같이 이 파라미터가 표현식으로 선언됨을 보여준다.
예제 4-17. 플러그인 파라미터 표현식
testFailureIgnore Set this to true to ignore a failure during \
testing. Its use is NOT RECOMMENDED, but quite \
convenient on occasion.
* Type: boolean
* Required: No
* Expression: ${maven.test.failure.ignore}
testing. Its use is NOT RECOMMENDED, but quite \
convenient on occasion.
* Type: boolean
* Required: No
* Expression: ${maven.test.failure.ignore}
이 표현식은 다음과 같이 –D 파라미터를 사용해서 명령행에서 세팅될 수 있다.
$ mvn test -Dmaven.test.failure.ignore=true
4.12.2. 단위 테스트 건너뛰기
단위 테스트를 보두 건너뛰도록 Maven을 설정하기 원할 수도 있다. 아마도 단위 테스트가 완료될 때까지 수분이 걸려서 결과를 만들기 전에 단위 테스트를 기다리기를 원치 않는 매우 큰 시스템을 가질 수도 있다. 혹은 여러 차례 실패하는 단위테스트를 가지는 기존 시스템으로 작업할 때 이를 수정하기 보다는 JAR 를 만들기를 원하는 경우일 수도 있다. Maven은 Surefire 플러그인의 skip 파라미터를 사용해서 단위 테스트들을 건너띄게 만들 수 있다. 명령행에서 테스트를 건너뛰려면, goal에 maven.test.skip 속성을 추가하면 된다.
$ mvn install -Dmaven.test.skip=true
...
[INFO] [compiler:testCompile]
[INFO] Not compiling test sources
[INFO] [surefire:test]
[INFO] Tests are skipped.
...
...
[INFO] [compiler:testCompile]
[INFO] Not compiling test sources
[INFO] [surefire:test]
[INFO] Tests are skipped.
...
Surefire 플러그인이 test goal에 도달했을 때 maven.test.skip 속성이 true로 세팅되었다면 단위 테스트를 건너뛴다. 단위 테스트를 건너뛰도록 메이븐을 설정하는 다른 방법은 예제 4-18에 나타난 것처럼 프로젝트의 pom.xml 에 설정을 추가하는 것이다. 이렇게 하려면 <build> 설정에 <plugin> 요소를 추가하면 된다.
예제 4-18. 단위 테스트 건너뛰기
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
4.13. 패키징된 명령행 어플리케이션의 빌드
이 책의 이전 4.8절에서 Maven Exec 플러그인을 사용해서 어플리케이션을 실행했다. 이 플러그인이 프로그램을 실행하고 어떤 결과를 만들었지만, 메이븐을 어플리케이션에 대한 실행 컨테이너로 생각해서는 안된다. 이 명령행 어플리케이션을 다른 사람들에게 배포한다면 JAR나 ZIP 혹은 TAR된 GZIP 파일의 압축파일로 배포하기 원할 것이다. 이 절은 프로젝트의 바이트코드와 모든 의존관계를 포함하는 배포가능한 JAR 파일로 만들어내기 위해 Maven Assembly 플러그인에 사전에 정의된 assembly descriptor 사용에 대한 절차를 보여준다.
어플리케이션에 대한 임의의 배포를 만들기 위해 Maven Assembly 플러그인을 사용할 수 있다. 사용자에 입맛에 맞는 assembly descriptor를 정의함으로써 원하는 형태로 프로젝트의 결과를 조합하는데 사용할 수 있다. 이 장에서 간단한 날씨 어플리케이션에 대해 좀 더 복잡한 형태를 만들어내는 변경된 assembly descriptor를 어떻게 만드는지를 보여줄 것이다. 이 장에서 사전에 정의된 jar-with-dependencies 형태를 사용하려고 한다. Assembly 플러그인을 설정하려면, pom.xml 에 기존 빌드 설정에 예제 4-19에 나타난 <plugin> 설정을 추가할 필요가 있다.
예제 4-19. Maven Assembly descriptor 설정
<project>
[...]
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
[...]
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
일단 위의 설정을 추가하면, 아래와 같이 mvn assembly:assembly를 실행해서 assembly를 빌드할 수 있다.
$ mvn install assembly:assembly
...
[INFO] [jar:jar]
[INFO] Building jar: ~/examples/simple-weather/target/simple-weather-1.0.jar
[INFO] [assembly:assembly]
[INFO] Processing DependencySet (output=)
[INFO] Expanding: \
.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into \
/tmp/archived-file-set.1437961776.tmp
[INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/\
commons-lang-2.1.jar
into /tmp/archived-file-set.305257225.tmp
... (Maven Expands all dependencies into a temporary directory) ...
[INFO] Building jar: \
~/examples/simple-weather/target/\
simple-weather-1.0-jar-with-dependencies.jar
...
[INFO] [jar:jar]
[INFO] Building jar: ~/examples/simple-weather/target/simple-weather-1.0.jar
[INFO] [assembly:assembly]
[INFO] Processing DependencySet (output=)
[INFO] Expanding: \
.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into \
/tmp/archived-file-set.1437961776.tmp
[INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/\
commons-lang-2.1.jar
into /tmp/archived-file-set.305257225.tmp
... (Maven Expands all dependencies into a temporary directory) ...
[INFO] Building jar: \
~/examples/simple-weather/target/\
simple-weather-1.0-jar-with-dependencies.jar
일단 assembly가 target/simple-weather-1.0-jar-with-dependencies.jar에 만들어지면, 명령행에서 다시 Main 클래스를 실행할 수 있다. 간단한 날씨 어플리케이션의 Main 클래스를 실행하려면, 프로젝트의 기반 디렉토리에서 다음을 실행한다.
$ cd target
$ java -cp simple-weather-1.0-jar-with-dependencies.jar \
org.sonatype.mavenbook.weather.Main 10002
0 INFO YahooRetriever - Retrieving Weather Data
221 INFO YahooParser - Creating XML Reader
399 INFO YahooParser - Parsing XML Response
474 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
New York, NY, US
Temperature: 44
Condition: Fair
Humidity: 40
Wind Chill: 40
*********************************
$ java -cp simple-weather-1.0-jar-with-dependencies.jar \
org.sonatype.mavenbook.weather.Main 10002
0 INFO YahooRetriever - Retrieving Weather Data
221 INFO YahooParser - Creating XML Reader
399 INFO YahooParser - Parsing XML Response
474 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
New York, NY, US
Temperature: 44
Condition: Fair
Humidity: 40
Wind Chill: 40
*********************************
jar-with-dependencies 형태는 간단한 날씨 프로젝트의 모든 바이트코드 뿐만 아니라 모든 의존관계의 압축된 바이트코르를 포함하는 단일 JAR 파일을 생성한다. 이러한 약간은 불편한 형태가 약 5,290 여개의 클래스를 포함하는 9MiB 짜리 JAR 파일을 만들어내지만, 메이븐으로 만든 어플리케이션에 대해 편리한 배포 형태로 제공한다. 이 책의 이후에 더 표준적인 배포를 만들어내는 assembly descriptor를 어떻게 생성하는지를 보여줄 것이다.
<<Pre Next>>
Elvis Lee에 의해 창작된 메이븐 가이드 은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이선스에 따라 이용할 수 있습니다.
www.sonatype.com의 저작물에 기초
반응형
'Homo Faber > Maven Definitive Guide' 카테고리의 다른 글
6장. 다중 프로젝트 (0) | 2008.10.30 |
---|---|
5장. 간단한 웹 어플리케이션 (2) | 2008.10.30 |
3장. 간단한 Maven 프로젝트 (0) | 2008.10.29 |
I부. 메이븐 예제 (0) | 2008.10.29 |
2장. 메이븐 설치와 실행 (0) | 2008.10.29 |