행복한 아빠

[Grails1.0 사용자 가이드] 9. 테스팅 본문

Grails

[Grails1.0 사용자 가이드] 9. 테스팅

행복한아빠 2008. 3. 12. 12:34

9. 테스팅

자동화된 테스트는 Grails의 핵심 부분으로 Groovy Tests를 이용하여 구현하였다. 따라서 Grails는 낮은 수준의 단위 테스트부터 높은 수준의 기능 테스트까지 쉽게 테스트를 만드는 여러 방법을 제공한다. 이 장은 Grails가 테스팅 관점에서 제공하는 차별화된 능력을 설명한다.

알아둬야 할 첫번째는 모든 create-* 명령어는 실제로 통합 테스트를 자동으로 생성하며 끝난다는 것이다. 예를 들어 아래와 같이 create-controller 명령어를 실행한다고 하자.

grails create-controller simple

Grails는 grails-app/controllers/SimpleController.groovy에 컨트롤러를 생성할 뿐만 아니라 test/integration/SimpleControllerTests.groovy에 통합 테스트도 생성한다. 어쨌거나 테스트 안에 로직을 만드는 것은 Grails가 할 수 없는 일이다! 이것은 개발자에게 남겨둔다.

이렇게 했다면 test-app 명령어로 모든 테스트를 실행할 수 있다.

grails test-app

위 명령어는 다음과 같은 결과를 만들어낸다.

-------------------------------------------------------
Running Unit Tests…
Running test FooTests...FAILURE
Unit Tests Completed in 464ms …
-------------------------------------------------------
Tests failed: 0 errors, 1 failures

test/reports 디렉토리에 위 결과 보고서가 만들어진다. 또한 테스트를 따로 돌리수 있는데 그러기 위해서는 Tests 접미어를 뺀 테스트 이름을 지정하면 된다.

grails test-app SimpleController

게다가 스페이스로 이름을 분리하여 지정하면 여러 개의 테스트들을 실행할 수도 있다.

grails test-app SimpleController BookController


9.1 단위 테스트
단위 테스트는 "구성단위" 수준의 테스트이다. 바꿔 말하면 주위 인프라를 고려하지 않고 개개의 메소드나 코드 블럭을 테스트하는 것을 말한다. Grails에서는 단위테스트와 통합테스트의 차이를 알아야 하는데 왜냐하면 Grails는 단위 테스트에서 통합테스트와 실행시간에 이루어지는 어떤 동적 메소드의 주입(inject)로 하지 않기 때문이다.

이 이유로 메소드를 흉내(mock)내기 위해 Groovy Mock이나 ExpandoMetaClass와 같은 것을 사용하는 것은 개발자의 몫이다.

예를 들어 BookController에 다음과 같은 action이 있다고 하자.

def show = {
    [ book : Book.get( params.id ) ]
}

params 객체와 get 메소드 둘 다 실행시간에 Grails가 제공하는 것이므로 단위테스트에서는 사용할 수 없다. 그러나 그것들을 흉내낼 수는 있다. 그럴려면 다음과 같이 ExpandoMetaClas를 사용한다.

void testShow() {
    // 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으로 각 테스트를 자동으로 설정한다. 이것으로 테스트를 수행할 수 있다. 예를 들어 다음 컨트롤러를 보자.

class FooController {
    def text = {
        render "bar"
    }
    def someRedirect = {
        redirect(action:"bar")
    }
}

이것을 테스트하려면

class FooControllerTests extends GroovyTestCase {
    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) 테스트를 통해 인터셉터는 따로 테스트해야 한다.

서비스와 함께 컨트롤러 테스트하기
컨트롤러가 서비스를 참조한다면 테스트에서 서비스를 명시적으로 초기화해야 한다.

아래 컨트롤러는 서비스를 사용한다.

class FilmStarsController {
    def populariryService
    def update = {
        // popularitySerivce로 뭔가를 한다.
    }
}

이것을 테스트하려면

class FilmStarsTests extends GroovyTestCase {
    def popularityService
    public void testInjectedServiceInController {
        def fsc = new FilmStartsController()
        fsc.popularityService = popularityService
        fsc.update()
    }
}


컨트롤러 명령객체(Command Object) 테스트하기
명령객체 테스트를 위해 request에 파라메터를 제공하고 액션을 파라메터 없이 호출하면 명령객체는 자동으로 작동할 것이다.

명령객체를 사용하는 컨트롤러를 보자

class AuthenticationController {
    def signup = { Signupform form -> ... }
}

다음과 같이 테스트할 수 있다.

def controller = new AuthenticationController()
controller.params.login = "marcpalmer"
controller.params.password = "secret"
controller.params.passwordConfirm = "secret"
controller.signup()

Grails는 마술과 같이 signup() 호출을 액션 호출로 여기고 가짜 request 파라메터로 명령객체를 채워준다(populate). 컨트롤러를 테스트하는 동안은 Grails가 제공하는 가짜 request와 함께 params도 변경할 수 있다.

컨트롤러와 render 메소드 테스트하기
액션의 몸체 안의 어느 지점에서도 render 메소드로 뷰를 렌더링할 수 있다. 예를 들어 다음 예제를 보자

def save = {
    def book = Book(params)
    if(book.save()) {
        // handle
    }
    else {
        render(view:"create", model:[book:book])
    }
}

이 예제에서 액션의 결과 모델을 반환 값으로 사용할 수 없다. 그러나 대신 컨트롤러의 modelAndView 속성에 저장할 수 있다. modelAndView 속성은 Spring MVC의 ModelAndView 클래스의 인스턴스이고 액션의 결과를 테스트하기 위해 이것을 사용할 수 있다.

def bookController = new BookController()
bookController.save()
def model = bookController.modelAndView.model.book


Request 데이터 흉내내기(Simulating)
REST 웹 서비스와 같은 request 데이터가 필요한 액션을 테스트할 경우 Spring MockHttpServletRequest 객체를 사용할 수 있다. 예를 들어 들어오는 request로 데이터바인딩을 수행하는 액션을 보자.

def create = {
    [book: new Book(params['book'])]
}

만일 'book' 파라메터를 XML request로 흉내내려면 다음과 같이 할 수 있다.

void testCreateWithXML() {
    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도 동일하다.

void testCreateWithJSON() {
     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
}

사용자 삽입 이미지
JSON을 사용할 때 바인드하기 위한 타입 이름을 지정하는 class 속성을 잊지 않도록 한다. XML에서는 <book> 노드의 이름을 암시적으로 사용하지만 JSON에서는 JSON 패킷의 일부로 이 속성이 필요하다.

REST 웹서비스 주제에 대한 자세한 정보는 REST 장을 보라.

웹플로우 테스트하기
웹플로우를 테스트하기 위해서는 Spring 웹플로우의 AbstractFlowExecutionTests클래스의 하위 클래스인 grails.test.WebFlowTestCase라는 특별한 장치가 필요하다.

사용자 삽입 이미지
WebFlowTestCase의 하위 클래스는 반드시 통합테스트이어야 한다.

예를 들어 다음의 간단한 흐름을 보자.

class ExampleController {
    def exampleFlow = {
        start {
            on("go") {
                flow.hello = "world"
            }.to "next"
        }
        next {
            on("back").to "start"
            on("go").to "end"
        }
        end()
    }
}

"흐름 정의"로 무엇을 사용할 것인지 테스트 장치에 알려줄 필요가 있다. 이것은 abstract getFlow 메소드를 재정의(override)하여 할 수 있다.

class ExampleFlowTests extends grails.test.WebFlowTestCase {
    def getFlow() { new ExampleController().exampleFlow }
    …
}

흐름 아이디를 지정할 필요가 있다면 getFlowId 메소드를 재정의할 수 있다. 그렇지 않을 경우 기본은 test이다.

class ExampleFlowTests extends grails.test.WebFlowTestCase {
    String getFlowId() { "example" }
    …
}

테스트에서 이것이 다 되었다면 startFlow 메소드로 흐름을 시작할 필요가 있다. startFlowViewSelection 객체를 반환한다.

void testExampleFlow() {
    def viewSelection = startFlow()
    assertEquals "start", viewSelection.viewName
    …
}

위에서 보듯 ViewSelection 객체의 viewName 속성으로 올바른 상태에 있는지 검사할 수 있다. 이벤트를 발생하려면 signalEvent 메소드를 사용한다.

void testExampleFlow() {
    …
    viewSelection = signalEvent("go")
    assertEquals "next", viewSelection.viewName
    assertEquals "world", viewSelection.model.hello
}

여기에서 우리는 "go" 이벤트를 실행하도록 흐름에 신호를 보냈다. 이것은 "next" 상태로 이동하게 한다. 예제에서 변이 액션은 플로우 범위에 hello 변수를 저장한다. 위에서 보듯 ViewSelection의 model 속성을 검사하여 이 변수의 값을 테스트할 수 있다.

태그 라이브러리 테스트하기
태그 라이브러리 테스트는 실제로 아주 단순하다. 왜냐하면 태그를 메소드로 호출하면 그 결과로 문자열로 반환하기 때문이다. 따라서 다음과 같은 태그 라이브러리가 있다고 하면

class FooTagLib {
    def bar =  { attrs, body ->
        out << "<p>Hello World!</p>"
    }
    def bodyTag =  { attrs, body ->
        out << "<${attrs.name}>"
        out << body()
        out << "</${attrs.name}>"    
    }
}

테스트는 다음과 같을 것이다.

class FooTagLibTests extends GroovyTestCase {
    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 렌더링의 결과를 테스트하기 위한 유틸리티 메소드들을 제공한다.

사용자 삽입 이미지
통합테스트에서만 GroovyPagesTestCase를 사용할 수 있다.

다음과 같은 날짜 포맷팅 태그 라이브러리를 보자.

class FormatTagLib {
    def dateFormat = { attrs, body ->
        out << new java.text.SimpleDateFormat(attrs.format) << attrs.date
    }
}

다음과 같이 간단히 테스트할 수 있다.

class FormatTagLibTests extends GroovyPagesTestCase {
    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 결과를 획득할 수도 있다.

class FormatTagLibTests extends GroovyPagesTestCase {
    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"를 할 필요가 있다. 다음 예를 들어보자.

void testQuery() {
    def books = [ new Book(title:"The Stand"), new Book(title:"The Shining")]
    books*.save()
    assertEquals 2, Book.list().size()
}

이 테스트는 실제로는 실패한다. 왜냐하면 save 호출은 호출될 때 실제로 Book 인스턴스를 저장하지 않는다. save 호출은 단지 미래 어느 시점에 이 인스턴스들을 저장해야 한다고 Hibernate에게 알려주는 것이다. 변화를 바로 적용하려면 "flush"를 해야 한다.

void testQuery() {
    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를 설치하려면 다음 명령어를 입력한다.

grails install-plugin webtest

그런 후 Web Test와 Grails를 사용하려면 어떻게 해야 하는지 설명한 위키의 레퍼런스를 참조하라.

---
원문: 9. Testing

0 Comments
댓글쓰기 폼