 |
|
서비스 빌드와 테스트
XML을 이용해 어떻게 작업할지 그리고 사용할 데이터 계층을 어떻게 할지 알았다. 이제 Restlet을 이용해 RESTful한 애플리케이션을 빌드하여 테스트 작업을 준비할 시간이다.
경기 서비스
Acme Racing에서는 고객(스폰서)들이 기존 경기뿐 아니라 새로 경기를 생성할 수 있길 바랄 것이라는 점을 다시 한 번 상기해보자. 이미 이런 행동를 지원할 RESTful한 URI를 머리 속에 그려두었을 것이다. 바로 /race였다.
RaceApplication 클래스의 Router 클래스를 통해 RacesResource 클래스에 이 URI를 연결했다.
-
getRepresentation()
-
allowPost()
-
post()
따라서 적절히 RacesResource라 부르는 클래스를 하나 만들고 org.restlet.resource.Resource를 확장한다.
Listing 13. RacesResource의 세 가지 인자
public class RacesResource extends Resource {
public RacesResource(Context context, Request request, Response response) {
super(context, request, response);
}
}
|
Restlet에는 자원의 표현을 어떻게 적절히 상호 통신할지 알려줘야 한다. XML은 자원 형식으로 서비스될 것이므로 XML 타입을 추가하여 Restlet에 지시해야 한다. Restlet에서 Variant는 Resource를 위한 형식을 나타낸다. 기본 클래스인 Resource에는 다양한 Variant 타입 추가를 쉽게 해주는 getVariants() 메서드가 담겨 있다. Listing 14처럼 생성자에 한 줄 추가한다.
Listing 14. XML을 한 형식으로 사용할 것임을 알림
this.getVariants().add(new Variant(MediaType.TEXT_XML));
|
Restlet 프레임워크에서는 이미지와 비디오 등 폭넓고 다양한 미디어 타입을 지원한다.
GET 요청 다루기
이제 클래스의 가장 손쉬운 행동, 즉 GET 요청을 다루는 작업을 구현할 때가 되었다. Listing 15에 보인 대로 getRepresentation() 메서드를 오버라이드하자.
Listing 15. getRepresentation() 오버라이드
public Representation getRepresentation(Variant variant) {
return null;
}
|
여러분도 알고 있겠지만 이 메서드는 Representation 타입을 반환하는데 구현체는 여러 개가 있다. StringRepresentation을 적당히 베껴 구현한 것 중 한 가지 구현체가 있는데 이 구현체만으로도 문자열을 표현할 것이고 여러분의 요구 조건은 충족시킬 것이다.
알다시피 이미 데이터베이스 사용을 지원하는 기존 도메인 모델을 이미 갖고 있는 상황이다. 또한 도메인 객체를 XML 문서로 변환해줄 RaceReporter라는 유틸리티 클래스도 작성되어 있다. 이 클래스의 racesToXml() 메서드는 Race 인스턴스들을 취해 Listing 16에 나타낸 XML 문서와 비슷한 형태의 String 표현을 반환한다.
Listing 16. XML 응답
<acme-races>
<races>
<race name="Leesburg 5K" date="2008-05-12" distance="3.1" id="5">
<uri>/races/5</uri>
<description/>
</race>
<race name="Leesburg 10K" date="2008-07-30" distance="6.2" id="6">
<uri>/races/6</uri>
<description/>
</race>
</races>
</acme-races>
|
사실 이 XML 문서는 여러분의 RESTful한 웹 서비스인 /race URI가 GET 요청으로 불려질 때 뭘 반환하는지 보여주는 한 예다.
따라서 여러분이 할 일은 하단의 데이터 저장소와 모든 경기 인스턴스 간의 정보 검색을 연결하는 것이다. 사실 이 시점이 되면 이미 테스트 코드도 작성할 수 있다.
서비스 테스트
Restlet 프레임워크를 이용하여 고객 인스턴스를 생성하고 이 인스턴스가 RESTful한 웹 서비스를 불러낼 수 있게끔 할 수 있다. 게다가 서비스가 내놓는 산출물이 제대로 된 XML 문서인지 확인하기 위해 XMLUnit을 이용해볼 수도 있다(참고자료 참조). 끝으로 아주 중요한 이야기지만 DbUnit을 사용하여 하부 데이터베이스를 어떤 상태 조건이 되게끔 할 수도 있다(그렇게 함으로서 항상 똑같은 XML 문서를 얻어낼 수 있게 된다. 참고자료 참조).
JUnit 4를 사용하여 Listing 17에서 보인 것처럼 XMLUnit과 DbUnit을 적절히 초기화하는 두 개의 픽스처(fixture)를 생성할 수 있다.
Listing 17. XMLUnit과 DbUnit 설정
@Before
public void setUpXMLUnit() {
XMLUnit.setControlParser(
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
XMLUnit.setTestParser(
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
XMLUnit.setSAXParserFactory(
"org.apache.xerces.jaxp.SAXParserFactoryImpl");
XMLUnit.setIgnoreWhitespace(true);
}
@Before
public void setUpDbUnit() throws Exception {
Class.forName("org.hsqldb.jdbcDriver");
IDatabaseConnection conn =
new DatabaseConnection(
getConnection("jdbc:hsqldb:hsql://127.0.0.1", "sa", ""));
IDataSet data = new FlatXmlDataSet(new File("etc/database/race-db.xml"));
try {
DatabaseOperation.CLEAN_INSERT.execute(conn, data);
} finally {
conn.close();
}
}
|
setUpDbUnit 메서드에서 데이터베이스의 XML 표현은 CLEAN_INSERT 명령을 통해 데이터베이스로 들어간다. 이 XML 파일은 여섯 가지 서로 다른 경기를 효과적으로 집어 넣는다. 따라서 GET에 대한 응답은 여섯 개 경기에 대한 하나의 XML 문서가 될 것이다.
다음으로 /race URI을 HTTP GET을 써서 부른 후, XML 응답을 얻고, XMLUnit의 Diff 클래스를 사용하여 통제에 사용할 XML 파일과 비교하는 테스트 케이스를 생성한다. Listing 18에 보였다.
Listing 18. XMLUnit을 써서 GET 응답 검증
@Test
public void getRaces() throws Exception {
Client client = new Client(Protocol.HTTP);
Response response =
client.get("http://localhost:8080/racerrest/race/");
Diff diff = new Diff(new FileReader(
new File("./etc/control-xml/control-web-races.xml")),
new StringReader(response.getEntity().getText()));
assertTrue(diff.toString(), diff.identical());
}
|
control-web-races.xml 파일은 웹 서비스에서 응답할 것으로 기대되는 XML이다. Listing 19에 나타내었다.
Listing 19. 통제에 사용할 XML 파일
<acme-races>
<races>
<race name="Mclean 1/2 Marathon" date="2008-05-12" distance="13.1" id="1">
<uri>http://localhost:8080/races/1</uri>
<description/>
</race>
<race name="Reston 5K" date="2008-09-13" distance="3.1" id="2">
<uri>http://localhost:8080/races/2</uri>
<description/>
</race>
<race name="Herndon 10K" date="2008-10-22" distance="6.2" id="3">
<uri>http://localhost:8080/races/3</uri>
<description/>
</race>
<race name="Leesburg 1/2 Marathon" date="2008-01-02" distance="13.1" id="4">
<uri>http://localhost:8080/races/4</uri>
<description/>
</race>
<race name="Leesburg 5K" date="2008-05-12" distance="3.1" id="5">
<uri>http://localhost:8080/races/5</uri>
<description/>
</race>
<race name="Leesburg 10K" date="2008-07-30" distance="6.2" id="6">
<uri>http://localhost:8080/races/6</uri>
<description/>
</race>
</races>
</acme-races>
|
물론 지금 이 상황에서 이 테스트를 하면 에러가 쏟아질 것이다. 아직 RESTful한 서비스를 구현하지 않았기 때문이다. 소스 다운로드에 포함된 앤트(Ant) 빌드 파일에는 WAR 파일 배치와 톰캣(Tomcat) 시작과 중지에 필요한 작업이 포함되어 있음을 주지하자(다운로드 참조). 이 작업은 테스트가 성공적으로 이뤄지는 데에는 선결 조건이다.
GET 요청 수행이 식은 죽 먹기라는 걸 알았을 것이다. 필요한 일이라곤 Race 도메인 객체에서 findAll 메서드를 불러낸 다음 호출 결과를 RaceReporter의 racesToXml() 메서드로 전달하는 게 전부다. 그러므로 Listing 20에 보인 것처럼 새로운 멤버 변수뿐 아니라 생성자에서 새로 초기화를 해 RacesResource 인스턴스를 갱신할 필요가 있다.
Listing 20. RaceReporter 추가를 잊지 말자.
public class RacesResource extends Resource {
private RaceReporter reporter;
public RacesResource(Context context, Request request, Response response) {
super(context, request, response);
this.getVariants().add(new Variant(MediaType.TEXT_XML));
this.reporter = new RaceReporter();
}
}
|
GET 요청 구현을 마무리 짓기가 아주 쉬워졌다. Listing 21에 보인 대로 getRepresentation 메서드에 세 줄만 추가하면 된다.
Listing 21. GET 요청 마무리
public Representation getRepresentation(Variant variant) {
Collection<Race> races = Race.findAll();
String xml = this.reporter.racesToXml(races);
return new StringRepresentation(xml);
}
|
믿거나 말거나 이게 전부다!
그러나 잠깐, 테스트하려면 애플리케이션을 배치해야 하지 않겠는가?
|