2014年4月24日木曜日

Programming in Scala, First Edition Chapter 14

14. Assertions and Unit Testing

14.1 Assertions
  assert()メソッドを使って書く。
  assert(condition)
    conditionが偽になるとAssertionError例外を投げる。
  assert(condition, explanation)
    AssertionErrorにexplanationからtoStringで取得した文字列を入れて投げる。
    explanationはAny。scalaでは全ての型にtoStringが実装されている。
  ensuring (w <= _.width)
    assert()は何処に入れても良いが、良くあるのはreturnの直前。
      この場合、assert()の後で保持したreturn値を返す様に書く必要がある。
    ensuring()を使えばこれが簡単に出来る。
    戻り値を引数に取ってBooleanを返すチェック関数を引数に取る。
    関数の戻り値をチェックに渡してtrueなら戻り値を関数の戻り値として返す。
      def function(): type = { ... } ensuring ((x: type): => {...}: Boolean)
    ?? ensuringに渡されているのは戻り値のimplicitly converted??
  assersionやensuringはJVMの-ea,-daのcommand-line flagsで有効化できる。
    有効にしたまま動かせば、実動作中に値の正常性をチェックできる。
    ただし、この章ではテストデータを使った外部のユニットテストを扱う。
  ?? assert()を使うのは関数型の処理順非依存に反しない??

14.2 Unit testing in Scala
  javaのunit test toolが使える。
    JUnit, TestNG
  scala用に作られたtoolもある
    ScalaTest, specs, ScalaCheck
    ?? 2008年の段階なので今はもっと良いものがあるかも??
  ここで扱うのはScalaTest
  ScalaTestでも幾つかのテスト方法がある。
  一番簡単なのはorg.scalatest.Suiteをmix-inしたクラスにtest methodsを定義する。
    import org.scalatest.Suite
    import Element.elem
    class ElementSuite extends Suite {
      def testUniformElement() {
        val ele = elem('x', 2, 3)
        assert(ele.width == 2)
      }
    }
  インタープリタからも実行できる。
    scala> (new ElementSuite).execute()
  ScalaTestではsubtypeでexecuteをoverrideして他のスタイルを提供する。
  関数値を使ったFunSuite
    import org.scalatest.FunSuite
    import Element.elem
    class ElementSuite extends FunSuite {
      test("elem result should have passed width") {
        val ele = elem('x', 2, 3)
        assert(ele.width == 2)
      }
    }
    testと言うメソッドがprimary constructorから呼び出される。
      {} はテスト用の関数値(by-name parameter)で登録されて後で実行される。
    テスト関数の名前を付ける必要が無い。
    テストの説明を記載出来る。

14.3 Informative failure reports
  assert(ele.width == 2)
    単に例外が投げられるだけでどんな値だったかは分からない。
  assert(ele.width === 2)
    "3 did not equal 2"等と出る。
  expect(2) {
    ele.width
  }
    "Expected 2, but got 3"等と出る。
  intercept[IllegalArgumentException] {
   elem('x', -2, 3)
  }
    IllegalArgumentExceptionが投げられないとNGになるテスト。

14.4 Using JUnit and TestNG
  JUnit
    Kent Beck and Erich Gammaが作ったもっともポピュラーなフレームワーク
      JUnit3と4がある。
    junit.framework.TestCaseをextendsすればscalaでも書ける。
      書いたテストケースはJunit上で実行出来る。
    org.scalatest.junit.JUnit3SuiteをextendsすればScalaTestのassertが使える。
      書いたテストケースはJunit上でもScalaTest上でも実行出来る。
      JUnit4はSection 29.2参照。
    JUnitWrapperSuite
      javaで書かれたJunitのテストをScalaTestで実行可能。
  TestNG
    Cedric Beust and Alexandru Popescu作のオープンソース
    Junitと同様にscalaで書いたりjavaで書いたものをScalaTest上で実行出来る。

14.5 Tests as specifications
  behavior-driven development (BDD)
    コードの振る舞いを人が読める仕様記載する事が強調されている。
    記載されている振る舞いを確かめるためのテストを付随させる。
  ScalaTestにはSpecというtraitでこれをサポートしている。
    import org.scalatest.Spec
    class ElementSpec extends Spec {
      describe("A UniformElement") {
        it("should have a width equal to the passed value") {
          val ele = elem('x', 2, 3)
          assert(ele.width === 2)
        }
        it("should throw an IAE if passed a negative width") {
        ...
    describeにsubject、テストの主題を記載して、
    itにbehavior、個々の振る舞いを記載する。
    テストの実行結果も可読性があるものになっている。
  The specs testing framework
    Eric Torreborre作のオープンソースで、違うBDDスタイルをサポートしている。
    import org.specs._
    object ElementSpecification extends Specification {
      "A UniformElement" should {
        "have a width equal to the passed value" in {
          val ele = elem('x', 2, 3)
          ele.width must be_==(2)
        }
        "throw an IAE if passed a negative width" in {
          elem('x', -2, 3) must
            throwA[IllegalArgumentException]
        ...
    "must be_==", "must throwA"の様なmatcherを色々用意されている。
      自作のmatcherを定義する事も出来る。
    trait org.specs.SpecsMatchersをmix-inすればScalaTest,JUnit,TestNGでも使える。
    specsは単独でも使えるし、ScalaTest, JUnitから実行する事も出来る。

14.6 Property-based testing
  ScalaCheck
    Rickard Nilsso作のオープンソース
  特定のpropertyがある条件でテストを満たすかを確認できる。
    import org.scalatest.prop.FunSuite
    import org.scalacheck.Prop._
    import Element.elem
    class ElementSuite extends FunSuite {
      test("elem result should have passed width", (w: Int) =>
        w > 0 ==> (elem('x', w, 3).width == w)
      )
    ...
  implication operator, ==> 包含演算子
    w > 0 ==> (elem('x', w, 3).width == w)
    ==>がポイント。左項が真なら右項も真になるはずと言う意味。
    ScalaCheckが実際は数百のパターンを生成して試験を行ってくれる!!
    ?? パターン生成方法と数は??
  ScalaTest's Checkers trait
    import org.scalatest.junit.JUnit3Suite
    import org.scalatest.prop.Checkers
    import org.scalacheck.Prop._
    import Element.elem
    class ElementSuite extends JUnit3Suite with Checkers {
      def testUniformElement() {
        check((w: Int) => w > 0 ==> (elem('x', w, 3).width == w))
        check((h: Int) => h > 0 ==> (elem('x', 2, h).height == h))
        ...
    checkメソッドを使えば複数propertyを使ったテストが出来る?
    ?? 上の例では複数propertyのは使っていない??

14.7 Organizing and running tests
  この章で紹介したフレームワークはテストを組織化する方法を提供している。
  ScalaTestについてちょっと触れてみる。詳しくはScalaTestのドキュメント参照。
  ScalaTestではSuiteを多段にネストして大規模なテストを構成する。
  Suiteはツリー構造になってrootを指定すると配下全てが実施される。
  ネスト登録は手動でも自動でも出来る。
    ネストされるクラスのメソッドを上書きする。
    登録用のSuperSuiteクラスのconstructorに渡す。
    package nameをScalaTest's Runnerに登録する。
      自動的にSuiteを探してroot Suite配下にネストする。
  ScalaTest's Runnerをコマンドラインから実行してテストを実施できる。
    antのtaskとしても実施出来る。
      antと言うのはjavaのアプリケーションをビルドするためのツール。
    Suiteの名前を指定する。
      prefixを指定するとRunnerが自動的にSuiteを探して実行する。
    オプションでテストのjarファイルのrunpathを指定できる。
    結果の表示をコントロールするreporterを一つもしくは複数指定できる。
    ScalaTestにはScalaTest自体の実装をテストするSuiteSuiteがある。
    $ scala -cp scalatest-0.9.4.jar org.scalatest.tools.Runner
          -p "scalatest-0.9.4-tests.jar" -s org.scalatest.SuiteSuite
      -cpはScalaTestのjarのクラスパス
      org.scalatest.tools.RunnerはRunner appのfully qualified name
      -pはテストSuiteのjarのrunpath
      -sは実行するてすとSuite

14.8 Conclusion
  2パターンのテスト方法
    実働コードにassertionを直接混ぜるケース
    別途テストSuiteを作成するケース
  javaのJUnitやTestNGも使えるし、ScalaTest,ScalaCheck,specsも使える。
  scalaの言語仕様から少し離れても説明べき重要な技術と感じている。

0 件のコメント:

コメントを投稿