Grails 1.2参考文書速読(17):テスト


TDDが広く知られている背景の下で、Grailsは自然にそれを無視することはありません.逆に、テストに多くのサポートを提供し、テストの作成の難しさと作業量を簡素化しました.
テストには多くのタイプがあり、Grailsが直接サポートしているのは2つです.ユニットテストと統合テストです.他のタイプのテストはプラグインで行われています.
ユニットテストはgrails create-unit-testコマンドを使用して作成され、ファイルは/test/unitの下に保存されます.統合テストはgrails create-integeration-testによって作成され、ファイルは/test/integrationにあります.すべてのcreate-*コマンドは、統合テストを自動的に作成します.この2つのテストのファイル名接尾辞はTestsです.
テストを実行するコマンドはgrails test-appです.使用例は次のとおりです.
  • は、すべてのテストを実行します.ユニットと統合テストです.grails test-app
  • 単一テストを実行する:grails test-appテスト名//Tests接尾辞を付けない
  • テストのセットを実行します:grails test-app test 1 test 2...//スペース間隔
  • ワイルドカード:grails test-app someを使用する.org.*;grails test-app some.org.**
  • ある方法をテストする:grails test-app SimpleController.testLogin

  • テストが完了するとtest/reportsにあるテストレポートが生成されます.
    Grailsはテストをステージとタイプに編成します:grails test-app phase:type
  • phase:unit、integration、functional、other
  • type:junitおよびintegrationフェーズ
  • 他のテストプラグインは、他のフェーズおよびタイプ
  • をもたらす可能性があります.
    junit統合テストを実行する例:grails test-app integration:integration.phaseもtypeも省略でき、すべてを表す.grails test-app unit:.より細粒度の指定例:grails test-app integration:unit:some.org.**.他のテストプラグインをインストールすると、他のフェーズとタイプが導入される場合があります.
    次に、ユニットテストに関する内容を見てみましょう.
    Grailsは、統合テストと実行時に注入されるユニットテストに動的メソッドを注入しません.ユニットテストを完了するために、開発者はMockを利用する必要があります.一般的な考え方は、Groovy MockとExpandoMetaClassを利用することです.幸いなことに、Grailsはユニットテストに大量のmock方法を提供し、ユニットテストを簡略化した.これらのmockはgrailsです.test.GrailsUnitTestCaseで提供されています.
    GrailsUnitTestCaseの多くのmockメソッドでは、まずmockForについて言及します.これは汎用的なMockメソッドで、以下のように使用されています.
    def strictControl = mockFor(MyService)
    strictControl.demand.someMethod(0..2) { String a, int b -> … }
    strictControl.demand.static.aStaticMethod {-> … }

    mockForを使用する一般的なモードは:mock.demand.(static.)?method(min…max){implement}.次のようになります.
  • static mock静的方法では
  • を用いる
  • min...maxは、メソッドが呼び出される最小回数と最大回数を指定します.デフォルトは1です.1は、1回のみ呼び出すことを示す
  • の後の閉パケットは
  • を実現することを示す.
    mockForのもう一つのパラメータはlooseで、mockが出てきたオブジェクトが厳密かどうかを示します.デフォルトはfalse、すなわち厳格です.厳密とはmockオブジェクト上のメソッド呼び出しに順序があることを意味する.
    def looseControl = mockFor(MyService, true)

    以上はmockが必要とする作業にすぎず、その結果、mockオブジェクト上でcreateMockを呼び出すことによって得られるターゲットオブジェクトが必要になります.mockでverifyを呼び出して、所望のメソッドが所望の方法で呼び出されたかどうかを検証します.
    以上の内容はeasyMockを使った読者には馴染みがあるはずです.mockForの例:
    def otherControl = mockFor(OtherService) 
    otherControl.demand.newIdentifier(1..1) {-> 
        return testId
    }
    // 
    def testService = new MyService() 
    // mock 
    testService.otherService = otherControl.createMock()
    // 
    def retval = testService.createSomething()

    Domain Classはあまりにもよく使われているため、Grailsはmockのサポートを提供しています.これはmockDomain(class,testInstances=)で、Domain Classをシミュレートするために使用されます.
  • testInstancesは、メモリ「データベース」
  • に相当します.
  • はCRUDとfindBy*の動作をシミュレートし、これらの動作はtestInstancesに従って
  • を行う.
  • 保存時にvalidate
  • が呼び出されます.
  • CriteriaおよびHQL
  • は実装されていない
    例:
    mockDomain(Item)
    def testInstances = Item.list()

    GrailsはDomain Classに対してCRUDダイナミックメソッドを提供しているため、これらの自動生成メソッドを単独でテストする意味は特に大きくありません.逆にDomain Classを作成するとき、私たちが最もよく書くのは制約です.そのため、個人的には、Domain Classに対するテストの多くは、主に制約に対するテストに現れていると思います.もちろん、Domain Classに他の方法を追加した場合は、あまり簡単ではない限り、これらの方法のテストも必要です.
    制約のテストの場合、Grailsは、Domain ClassおよびCommand Objectの制約テストに特化したmockForConstraintsTests(class,testInstances=)を提供します.Grailsはvalidateメソッドのみをシミュレートします.
    mockForConstraintsTests(Book, [ existingBook ])
    def book = new Book() 
    assertFalse book.validate()
    // , 
    assertEquals "nullable", book.errors["title"] 
    assertEquals "nullable", book.errors["author"]

    他の一般的なmockメソッド:
  • mockLogging(class,enableDebug=false)、アナログlog属性
  • mockController(class)、アナログController、ControllerUnitTestCaseと組み合わせて
  • を使用
  • mockTagLib(class)、シミュレーションTagLib、TagLibUnitTestCaseと組み合わせて
  • を使用
    以上のユニットテストの議論は主にGrails Mock法の紹介に集中しており,どのように書くかはjunitテストを書くこととあまり変わらない.次に、統合テストを見てみましょう.
    Grails統合テスト環境とランタイム環境は全く同じですが、Test環境の構成を使用します.リクエストなどのオブジェクトについても、mockオブジェクトで完了します.request、response、sessionはそれぞれ対応しています.
  • MockHttpServletRequest
  • MockHttpServletResponse
  • MockHttpSession

  • 注意すべきポイント:
  • では、ブロッキングは呼び出されず、機能テストを使用してテストされます.
  • Controllerが参照するサービスには、明示的な注入が必要です.
  • RequestのParamsを使用してCommandオブジェクトを構築します.

  • 統合テストの例:
  • Controller:
    class FooController { 
            def text = {
             render "bar"
         }
         def someRedirect = {
          redirect(action:"bar")
         }
        }
  • テスト:
    class FooControllerTests extends GroovyTestCase {  
            void testText() {
             def fc = new FooController()
             // action
             fc.text()
             assertEquals "bar",
                // 
                   fc.response.contentAsString
         } 
         void testSomeRedirect() {
              def fc = new FooController()
              fc.someRedirect()
              assertEquals "/foo/bar", 
                        fc.response.redirectedUrl
         }
        }
  • サービス付きControllerをテストする例:
    class FilmStarsTests extends GroovyTestCase {
        def popularityService  // grails 
        public void testInjectedServiceInController () {
            def fsc = new FilmStarsController()
            // 
            fsc.popularityService = popularityService
            ……
        }
    }

    Command Objectの場合:Paramsコンテンツを構築し、CommandオブジェクトとDomain Classをシミュレートします.
    class AuthenticationController {
        def signup = { SignupForm form -> …… }
    }
    // 
    def controller = new AuthenticationController()
    controller.params.login = "marcpalmer"
    controller.params.password = "secret"
    controller.params.passwordConfirm = "secret"
    controller.signup()

    responseコンテンツのテスト(例):
  • Controllerの例.response.contentAsString
  • Controllerの例.response.redirectedUrl

  • テストレンダー:
  • render(view:"create",model:[book:book]):Controllerインスタンス.modelAndView.model.book,Controllerインスタンス.modelAndView.view
  • [book:new Book(params['book']]):Controllerインスタンス.book

  • シミュレーションRequest、まずControllerのコードを見てみましょう.
    def create = {
     [book: new Book(params['book']) ]
    }

    テストコードの総フレームワーク:
    void testCreate() {
        def controller = new BookController()
      Request // 
     def model = controller.create()
     assert model.book
     assertEquals "The Stand", model.book.title
    }

    XMLシミュレーションを使用する方法(最後にgetBytesを呼び出すことに注意):
    controller.request.contentType = 'text/xml'
    controller.request.contents = '''
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <book>
     <title>The Stand</title>
     ...
    </book>
    '''.getBytes()

    JSONシミュレーションを使用する(最後にgetBytesを呼び出すことに注意):
    controller.request.contentType = "text/json"
    // class , domain class
    //XML 
    controller.request.content = 
      '{"id":1,"class":"Book","title":"The Stand"}'
      .getBytes()

    Web Flowは複雑なページストリームを実現し、そのテストにはGrailsでgrailsを使用する.test.WebFlowTestCase.その中で、注意しなければならない方法は次のとおりです.
  • getFlow、どのWebFlow定義を使用するかを指定する
  • getFlowId、web flow id
  • を指定
  • startFlow、web flow
  • を起動
  • signalEvent、トリガイベント
  • 例を見てみましょう.
  • Web Flow定義:
    class ExampleController {
            def exampleFlow = {
                start {
                    on("go") {
                        flow.hello = "world"
                    }.to "next"
                }
                next {
                    on("back").to "start"
                    on("go").to "end"
                }
                end()
            }   
        }
  • 試験例:
    class ExampleFlowTests extends grails.test.WebFlowTestCase {
         def getFlow() { new ExampleController().exampleFlow }
         String getFlowId() { "example" }
            void testExampleFlow() {
                def viewSelection = startFlow()
                assertEquals "start", viewSelection.viewName
                viewSelection = signalEvent("go")
              assertEquals "next", viewSelection.viewName
              assertEquals "world", viewSelection.model.hello
            }
        }
  • ラベルライブラリのテストには、通常の方法としてテストする方法と、ページ要素としてテストする方法の2つがあります.
  • タグライブラリ
    class FooTagLib {
           def bar =  { attrs, body ->
               out << "<p>Hello World!</p>"
           }   
           def bodyTag =  { attrs, body ->
              out << "<${attrs.name}>"
              out << body()
              out << "</${attrs.name}>"
           }
        }
  • は通常の方法として試験を行い、ベースクラスGroovyTestCase
    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!" 
                            }
            }
        }
  • はページ要素としてテストを行い、統合テストに使用され、ベースクラス:grails.test.GroovyPagesTestCase
    class FormatTagLibTests extends GroovyPagesTestCase {
            void testTag() {
                def template = '<g:foo />'      
                assertOutputEquals( '<p>Hello World!</p>', template)
                //applyTemplate 
                def result = applyTemplate( template)
                assertEquals '<p>Hello World!</p>', result
            }
        }

  • 統合テストでDomain Classを使用する場合は、明示的なflushが必要であり、永続化を確保する必要があります.そうしないとクエリーできない可能性があります.