행복한 아빠
[Grails1.0 사용자 가이드] 9. 테스팅 본문
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding
9. 테스팅
자동화된 테스트는 Grails의 핵심 부분으로 Groovy Tests를 이용하여 구현하였다. 따라서 Grails는 낮은 수준의 단위 테스트부터 높은 수준의 기능 테스트까지 쉽게 테스트를 만드는 여러 방법을 제공한다. 이 장은 Grails가 테스팅 관점에서 제공하는 차별화된 능력을 설명한다.
알아둬야 할 첫번째는 모든 create-* 명령어는 실제로 통합 테스트를 자동으로 생성하며 끝난다는 것이다. 예를 들어 아래와 같이 create-controller 명령어를 실행한다고 하자.
Grails는 grails-app/controllers/SimpleController.groovy에 컨트롤러를 생성할 뿐만 아니라 test/integration/SimpleControllerTests.groovy에 통합 테스트도 생성한다. 어쨌거나 테스트 안에 로직을 만드는 것은 Grails가 할 수 없는 일이다! 이것은 개발자에게 남겨둔다.
이렇게 했다면 test-app 명령어로 모든 테스트를 실행할 수 있다.
위 명령어는 다음과 같은 결과를 만들어낸다.
Running Unit Tests…
Running test FooTests...FAILURE
Unit Tests Completed in 464ms …
-------------------------------------------------------
Tests failed: 0 errors, 1 failures
test/reports 디렉토리에 위 결과 보고서가 만들어진다. 또한 테스트를 따로 돌리수 있는데 그러기 위해서는 Tests 접미어를 뺀 테스트 이름을 지정하면 된다.
게다가 스페이스로 이름을 분리하여 지정하면 여러 개의 테스트들을 실행할 수도 있다.
9.1 단위 테스트
단위 테스트는 "구성단위" 수준의 테스트이다. 바꿔 말하면 주위 인프라를 고려하지 않고 개개의 메소드나 코드 블럭을 테스트하는 것을 말한다. Grails에서는 단위테스트와 통합테스트의 차이를 알아야 하는데 왜냐하면 Grails는 단위 테스트에서 통합테스트와 실행시간에 이루어지는 어떤 동적 메소드의 주입(inject)로 하지 않기 때문이다.
이 이유로 메소드를 흉내(mock)내기 위해 Groovy Mock이나 ExpandoMetaClass와 같은 것을 사용하는 것은 개발자의 몫이다.
예를 들어 BookController에 다음과 같은 action이 있다고 하자.
[ book : Book.get( params.id ) ]
}
params 객체와 get 메소드 둘 다 실행시간에 Grails가 제공하는 것이므로 단위테스트에서는 사용할 수 없다. 그러나 그것들을 흉내낼 수는 있다. 그럴려면 다음과 같이 ExpandoMetaClas를 사용한다.
// static get method를 흉내
Book.metaClass.static.get = { Long id ->
assert id == 10
new Book(id:id, title:"The Stand")
}
// params 객체를 흉내
BookController.metaClass.getParams = {-> [id:10]}
def controller = new BookController()
def model = controller.show()
assert model
assert model.book
assertEquals 10, model.book.id
assertEquals "The Stand", model.book.title
}
가짜 인스턴스를 반환하는 get 메소드를 어떻게 구현하여 제공하는지 그리고 어떻게 이 구현으로 테스트 단언(assertion)을 사용했는지 주목하라. 또한 어떻게 맵을 사용하여 params 객체의 가짜 인스턴스를 제공했는지 주의하라.
9.2 통합 테스트
통합테스트는 Grails의 모든 환경을 접근할 수 있다는 점에서 단위테스트와 다르다. Grails는 통합테스트를 위해 메모리 HSQLDB 데이터베이스를 사용하고 각 테스트마다 데이터베이스에서 모든 데이터를 제거한다.
컨트롤러 테스트하기
컨트롤러를 테스트하기 위해 먼저 Spring Mock 라이브러리를 이해해야 한다.
본질적으로 Grails는 MockHttpServletRequest, MockHttpServletResponse 그리고 MockHttpSession으로 각 테스트를 자동으로 설정한다. 이것으로 테스트를 수행할 수 있다. 예를 들어 다음 컨트롤러를 보자.
def text = {
render "bar"
}
def someRedirect = {
redirect(action:"bar")
}
}
이것을 테스트하려면
void testText() {
def fc = new FooController()
fc.text()
assertEquals "bar", fc.response.contentAsStrng
}
void testSomeRedirect() {
def fc = new FooController()
fc.someRedirect()
assertEquals "/foo/bar", fc.response.redirectedUrl
}
}
위의 경우응답은 MockHttpServletResponse의 인스턴스이고 이것으로 응답으로 출력한 내용을 획득하기 위해 contentAsString를 사용하거나 예제의 리다이렉트 URL을 획득하기 위해 redirectedUrl을 사용할 수 있다. 실제 버전과 다르게 Servlet API의 이 가짜(mocked) 버전들은 변경할 수(mutable) 있기에 contextPath 등과 같은 request의 속성을 설정할 수 있다.
통합 테스트동안 액션을 호출할 때 Grails는 인터셉터들을 자동으로 호출하지 않는다. 필요하다면 기능(functional) 테스트를 통해 인터셉터는 따로 테스트해야 한다.
서비스와 함께 컨트롤러 테스트하기
컨트롤러가 서비스를 참조한다면 테스트에서 서비스를 명시적으로 초기화해야 한다.
아래 컨트롤러는 서비스를 사용한다.
def populariryService
def update = {
// popularitySerivce로 뭔가를 한다.
}
}
이것을 테스트하려면
def popularityService
public void testInjectedServiceInController {
def fsc = new FilmStartsController()
fsc.popularityService = popularityService
fsc.update()
}
}
컨트롤러 명령객체(Command Object) 테스트하기
명령객체 테스트를 위해 request에 파라메터를 제공하고 액션을 파라메터 없이 호출하면 명령객체는 자동으로 작동할 것이다.
명령객체를 사용하는 컨트롤러를 보자
def signup = { Signupform form -> ... }
}
다음과 같이 테스트할 수 있다.
controller.params.login = "marcpalmer"
controller.params.password = "secret"
controller.params.passwordConfirm = "secret"
controller.signup()
Grails는 마술과 같이 signup() 호출을 액션 호출로 여기고 가짜 request 파라메터로 명령객체를 채워준다(populate). 컨트롤러를 테스트하는 동안은 Grails가 제공하는 가짜 request와 함께 params도 변경할 수 있다.
컨트롤러와 render 메소드 테스트하기
액션의 몸체 안의 어느 지점에서도 render 메소드로 뷰를 렌더링할 수 있다. 예를 들어 다음 예제를 보자
def book = Book(params)
if(book.save()) {
// handle
}
else {
render(view:"create", model:[book:book])
}
}
이 예제에서 액션의 결과 모델을 반환 값으로 사용할 수 없다. 그러나 대신 컨트롤러의 modelAndView 속성에 저장할 수 있다. modelAndView 속성은 Spring MVC의 ModelAndView 클래스의 인스턴스이고 액션의 결과를 테스트하기 위해 이것을 사용할 수 있다.
bookController.save()
def model = bookController.modelAndView.model.book
Request 데이터 흉내내기(Simulating)
REST 웹 서비스와 같은 request 데이터가 필요한 액션을 테스트할 경우 Spring MockHttpServletRequest 객체를 사용할 수 있다. 예를 들어 들어오는 request로 데이터바인딩을 수행하는 액션을 보자.
[book: new Book(params['book'])]
}
만일 'book' 파라메터를 XML request로 흉내내려면 다음과 같이 할 수 있다.
def controller = new BookController()
controller.request.contentType = 'text/xml'
controller.request.contents = '''<?xml version="1.0" encoding="ISO-8859-1"?>
<book>
<title>The Stand</title>
…
</book>
'''.getBytes() // note we need the bytes
def model = controller.create()
assert model.book
assertEquals "The Stand", model.book.title
}
JSON request도 동일하다.
def controller = new BookController()
controller.request.contentType = "text/json"
controller.request.content = '{"id":1,"class":"Book","title":"The Stand"}'.getBytes()
def model = controller.create()
assert model.book
assertEquals "The Stand", model.book.title
}
REST 웹서비스 주제에 대한 자세한 정보는 REST 장을 보라.
웹플로우 테스트하기
웹플로우를 테스트하기 위해서는 Spring 웹플로우의 AbstractFlowExecutionTests클래스의 하위 클래스인 grails.test.WebFlowTestCase라는 특별한 장치가 필요하다.
예를 들어 다음의 간단한 흐름을 보자.
def exampleFlow = {
start {
on("go") {
flow.hello = "world"
}.to "next"
}
next {
on("back").to "start"
on("go").to "end"
}
end()
}
}
"흐름 정의"로 무엇을 사용할 것인지 테스트 장치에 알려줄 필요가 있다. 이것은 abstract getFlow 메소드를 재정의(override)하여 할 수 있다.
def getFlow() { new ExampleController().exampleFlow }
…
}
흐름 아이디를 지정할 필요가 있다면 getFlowId 메소드를 재정의할 수 있다. 그렇지 않을 경우 기본은 test이다.
String getFlowId() { "example" }
…
}
테스트에서 이것이 다 되었다면 startFlow 메소드로 흐름을 시작할 필요가 있다. startFlow는 ViewSelection 객체를 반환한다.
def viewSelection = startFlow()
assertEquals "start", viewSelection.viewName
…
}
위에서 보듯 ViewSelection 객체의 viewName 속성으로 올바른 상태에 있는지 검사할 수 있다. 이벤트를 발생하려면 signalEvent 메소드를 사용한다.
…
viewSelection = signalEvent("go")
assertEquals "next", viewSelection.viewName
assertEquals "world", viewSelection.model.hello
}
여기에서 우리는 "go" 이벤트를 실행하도록 흐름에 신호를 보냈다. 이것은 "next" 상태로 이동하게 한다. 예제에서 변이 액션은 플로우 범위에 hello 변수를 저장한다. 위에서 보듯 ViewSelection의 model 속성을 검사하여 이 변수의 값을 테스트할 수 있다.
태그 라이브러리 테스트하기
태그 라이브러리 테스트는 실제로 아주 단순하다. 왜냐하면 태그를 메소드로 호출하면 그 결과로 문자열로 반환하기 때문이다. 따라서 다음과 같은 태그 라이브러리가 있다고 하면
def bar = { attrs, body ->
out << "<p>Hello World!</p>"
}
def bodyTag = { attrs, body ->
out << "<${attrs.name}>"
out << body()
out << "</${attrs.name}>"
}
}
테스트는 다음과 같을 것이다.
void testBarTag() {
assertEquals "<p>Hello World!</p>", new FooTagLib().bar(null,null)
}
void testBodyTag() {
assertEquals "<p>Hello World!</p>", new FooTagLib().bodyTag(name:"p") {
"Hello World!"
}
}
}
두번째 예제 testBodyTag에서 태그의 몸체를 반환하는 블럭을 전달하는 것에 주목하라. 이것은 body를 문자열로 표현하기 위해 사용하기 좋다.
GroovyPagesTestCase에서 태그 라이브러리 테스트하기
위와 같이 간단하게 태그 라이브러리를 테스트하는 것에 더해 태그 라이브러리를 테스트 하기 위해 grails.test.GroovyPagesTestCase를 사용할 수도 있다.
GroovyPagesTestCase 클래스는 일반 GroovyTestCase 클래스의 하위 클래스이고 GSP 렌더링의 결과를 테스트하기 위한 유틸리티 메소드들을 제공한다.
다음과 같은 날짜 포맷팅 태그 라이브러리를 보자.
def dateFormat = { attrs, body ->
out << new java.text.SimpleDateFormat(attrs.format) << attrs.date
}
}
다음과 같이 간단히 테스트할 수 있다.
void testDateFormat() {
def template = '<g:dateFormat format="dd-MM-yyyy" date="${myDate}" />'
def testDate = … // create the date
assertOutputEquals( '01-01-2008', template, [myDate:testDate] )
}
}
또한 GroovyPagesTestCase 클래스의 applyTemplate 메소드를 이용하여 GSP 결과를 획득할 수도 있다.
void testDateFormat() {
def template = '<g:dateFormat format="dd-MM-yyyy" date="${myDate}" />'
def testDate = … // create the date
def result = applyTemplate( template, [myDate:testDate] )
assertEquals '01-01-2008', result
}
}
도메인 클래스 테스트
도메인 클래스 테스트는 보통 GORM API를 사용하는 간단한 문제이다. 하지만 몇 가지 알아둬야 할 것들이 있다. 첫번째로 질의를 테스트하기 위해서는 현재 상태를 데이터베이스에 저장하기 위해 종종 "flush"를 할 필요가 있다. 다음 예를 들어보자.
def books = [ new Book(title:"The Stand"), new Book(title:"The Shining")]
books*.save()
assertEquals 2, Book.list().size()
}
이 테스트는 실제로는 실패한다. 왜냐하면 save 호출은 호출될 때 실제로 Book 인스턴스를 저장하지 않는다. save 호출은 단지 미래 어느 시점에 이 인스턴스들을 저장해야 한다고 Hibernate에게 알려주는 것이다. 변화를 바로 적용하려면 "flush"를 해야 한다.
def books = [ new Book(title:"The Stand"), new Book(title:"The Shining")]
books*.save(flush:true)
assertEquals 2, Book.list().size()
}
이 경우 flush 아규먼트를 true로 전달하기에 바로 업데이트되고 바로 후에 질의가 가능한다.
9.3 기능(functional) 테스트
기능테스트는 실제 구동하는 애플리케이션을 테스트하는 것이고 자동화하기가 더 어렵다. Grails는 특별히 기능테스트에 대한 지원은 없다. 그러나 Canno WebTest를 플러그인으로 지원한다.
Web Test를 설치하려면 다음 명령어를 입력한다.
그런 후 Web Test와 Grails를 사용하려면 어떻게 해야 하는지 설명한 위키의 레퍼런스를 참조하라.
---
원문: 9. Testing
'Grails' 카테고리의 다른 글
[Grails1.0 사용자 가이드] 11. 보안 (0) | 2008.03.12 |
---|---|
[Grails1.0 사용자 가이드] 10. 국제화 (0) | 2008.03.12 |
[Grails1.0 사용자 가이드] 9. 테스팅 (0) | 2008.03.12 |
[Grails1.0 사용자 가이드] 8. 서비스 계층 (0) | 2008.03.12 |
[Grails1.0 사용자 가이드] 7. 유효성검사 (0) | 2008.03.11 |
[Grails1.0 사용자 가이드] 6. 웹 계층 - 7 (12) | 2008.03.10 |