LL脳がscalaの勉強を始めたよ その43


Scalaコップ本の14章の続きをやりますよー、今回はScalaTestのSpecとかScalaCheckとかそのへんですねー

仕様としてのテスト

プログラムの仕様とセットでテストを用意する開発手法なんかをよく聞きますが、今回は振る舞い駆動開発(behavior-driven development:BDD)にフィットしたScalaTestのテスト用トレイトSpecについてです。

BDDでは以下の2点を重視するみたいですねー

  • コードに対して期待される振る舞いを人間が理解できる仕様にまとめる
  • コードの振る舞いが仕様に合っているか確かめるテストを実施

Specでは振る舞いに対するテストを進めやすくするみたいデス、とりあえずサンプルやってみますかねー

// テスト対象のクラス
class Hoge{
  def moge = 2
  def huga() {
    try {
      throw new IllegalArgumentException
    }catch{
      case ex:IllegalArgumentException => println("想定内のエラーです")
    }
  }
}
// Specでテストをしますよ
import org.scalatest.Spec
class HogeSpec extends Spec {
  // 仕様説明部
  describe(" A Hoge Class"){
    // 仕様指定部その1
    it("mogeの結果が2になるかどうか"){
      val h = new Hoge
      assert(h.moge === 2)
    }
    // 仕様指定部その2
    it("mogeの結果が期待値の2になるかどうかテスト"){
      val h = new Hoge
      expect(2){h.moge}
    }
    // 仕様指定部その3
    it("想定内のエラーが発生するかどうかのテスト"){
      val h = new Hoge
      intercept[IllegalArgumentException]{
        h.huga()
      }
    }
  }
}

テスト実行結果です、これは見やすい(`・ω・´)

scala> (new HogeSpec).execute()
// 仕様説明
A Hoge Class 
// 仕様指定
- mogeの結果が2になるかどうか
- mogeの結果が期待値の2になるかどうかテスト
- 想定内のエラーが発生するかどうかのテスト

ちなみにエラーが見つかるとこんな感じですねー

scala> (new HogeSpec).execute()
A Hoge Class 
// 一つめのassertで強引にエラーを発生させてみましたよ
- mogeの結果が3になるかどうか *** FAILED *** (<console>:10)
  org.scalatest.TestFailedException: 2 did not equal 3
  ...
  at line12$object$$iw$$iw$HogeSpec$$anonfun$1$$anonfun$apply$1.apply(<console>:10)
  at line12$object$$iw$$iw$HogeSpec$$anonfun$1$$anonfun$apply$1.apply(<console>:8)
  at org.scalatest.Spec$$anon$2.apply(Spec.scala:1388)
  at org.scalatest.Suite$class.withFixture(Suite.scala:1509)
  at line12$object$$iw$$iw$HogeSpec.withFixture(<console>:6)
  at org.scalatest.Spec$class.runTest(Spec.scala:1385)
  at line12$object$$iw$$iw$HogeSpec.runTest(<console>:6)
  ...
- mogeの結果が期待値の2になるかどうかテスト
- 想定内のエラーが発生するかどうかのテスト

これはいいなぁ、しっかり使えるようにしよう(`・ω・´)

テストフレームワークspecs

似た様なものでオープンソースのBDD用テストフレームワークにspecsってのもあるみたいなんだけども…例によって動かない(´;ω;`)ので簡単にソースのみ書いてみますよ

import org.specs._
object HogeSpecification extends Specification {
  // 仕様説明部
  " A Hoge Class" should {
    // 仕様指定部その1
    "mogeの結果が2になるかどうか" in {
      val h = new Hoge
      h.moge must be_== (2)
    }
    // 仕様指定部その2
    "想定内のエラーが発生するかどうかのテスト" in {
      val h = new Hoge
      h.huga() must throwA[IllegalArgumentException]
    }
  }
}

上でSpec用に書いたのを(省略して)書き直してみましたよー、若干自然言語っぽくなりましたなー。うん、でも初心者的にはしばらくSpecでいいや(´・ω・`)動くし

動かないのはどうもJUnitとかそのへんをイロイロ使ってるみたいなのでソレがらみかな…と。いろいろなjarを読み込んで使うみたいなので依存関係とかかなぁ…とりあえずオフィシャルはこちらなので、時間があるときにでも触ってみたいと思います(`・ω・´)、オフィシャルには使い方なんかもまとまってるしね

プロパティーベースのテスト

Scalaテストツールとしては、テスト中のコードが従わなければならない性能(プロパティー)を指定するScalaCheckってのもあるみたいデス

内容的には条件に当てはまるテストデータを自動生成して、それを基にテストしてくれるみたいです。テスト条件は==>(含意演算子)を使ってこんな感じで書きますデス

// 左辺が満たされれば右辺になりマス
m > 0 ==> (m + 1 > 0) 

ちなみにこれまでと同じようなテストデータを書くとこんな風味ですかね…コップ本のサンプルが上手く動かせなかったので変えてますよー

// テスト対象のクラス
class Hoge{
  def moge = 2
}
// ScalaCheckしますよ
import org.scalatest.FunSuite
import org.scalacheck.Prop._
class HogeSuite extends FunSuite {
  test("mogeの値をつかったテストをしますよ"){
   val h = new Hoge
   val p = forAll{(m:Int) => 2 < m ==> (h.moge < m)}
   p.check
  }
}

実行結果はこんな感じですー

scala> (new HogeSuite).execute
Test Starting - HogeSuite: mogeの値をつかったテストをしますよ
+ OK, passed 100 tests.                                                       
Test Succeeded - HogeSuite: mogeの値をつかったテストをしますよ

失敗するとこんな感じみたいですねー

scala> (new HogeSuite).execute
Test Starting - HogeSuite: mogeの値をつかったテストをしますよ
! Falsified after 0 passed tests.                                      
// 失敗した値を出力しますよー       
> ARG_0: 1 (orig arg: 2)
Test Succeeded - HogeSuite: mogeの値をつかったテストをしますよ

使い方によってはエラー時にAssertionErrroが出せたりするらしいので便利そうですね…そこらへんが上手くやれなかったので今後の課題としたいと思いますが(´・ω・`)サンプルが上手く動かんのです…orz

JUnitとも連携できますよー

JUnit3Suiteと組み合わせることでJUnitでも実行出来るそうですー、JUnitなんでパスしますけども(´;ω;`)

いじょー

とりあえず単体テスト関係の紹介が漸く終わりー、次回は実際にScalaTestのアプローチについてやっていきますよー、それでようやく14章が終わりになる…はず...orz