akka unit testing
TRANSCRIPT
メニュー
1. Unit Testinga. Unit Testingとは
b. Scalaのテストフレームワーク
2. ScalaTesta. テスト表記スタイル
b. テストケースへのタグ付け
c. fixtured. テストケースの共有
3. Akka TestKit
Unit Testing=単体テストとは
モジュール単位、あるいは複数モジュールを組み合わせて出来ているモジュール単位
毎に対して行われるテスト。
ある値を入力したときに期待された値と、実際に返された値を、ひとつひとつチェックして
いく作業のこと。
補足:モジュール単位(1)
以下はモジュール単位のテストと呼べる。
def capitalizeFirst(str: String): String = str.head.toUpper + str.tail
capitalizeFirst(“hoge”) should equal (“Hoge”)
補足:モジュール単位(2)
以下はモジュール単位ではない。
def capitalizeFirst(str: String): String = str.head.toUpper + str.tail
def decorate(strs: List[String]): String = strs.mkString(“--->”, “&”, “<---”)
decorate(List(capitalizeFirst(“hoge”))) should equal (“--->Hoge<---”)
// capitalizeFirstとdecorateは別々に検証されるべき
Unit Testingのテストコード
単体テストコード……モジュールの呼び出し、期待値・実返り値の突き合わせの寄せ集め
……をバリバリ定義し、それを動作検証の拠り所にしながらゴリゴリ開発
メリット
1. 早期の問題検出
2. コードの変更を促進
3. 単体レベルでの正常動作の証明(e.g. 結合レベルのテストの下ごしらえ)
4. ライブラリ使用方法のマニュアル代わり
5. 設計方針の導出(※テストファーストの場合)
Scalaの有名ドコロのテストフレームワーク
● ScalaTest● Specs2● ScalaCheck● JUnit (Java)● TestNG (Java)
Playをはじめ、ScalaTestが標準になりつつある。
ScalaTest
特徴
● 多彩な表記方法/DSL● テストケースに対するタグ付け
● fixtureによるテスト下準備の共通化
● Sharing Testsによるテスト項目のパターン化
● Selenium/Mockingフレームワーク利用のシンタックスシュガー
テストコード用DSL
● Matchersをミックスインすることで利用できる多彩な等価演算子/Assert○ Array(1, 2) == Array(1, 2)はfalse○ Array(1, 2) should equal(Array(1, 2))はtrue○ “hoogge” should startWith regex (“h(o*)”)○○ val a = 1; a should be < 2; a should be >= 0○○ val file = new File(“./hoge.txt”); file should be a ‘file○ val file = new File(“./dir”); file should be a ‘directory○ ...
テストケースのタグ付け
● 主なメリットは、テストケースを色分けして実行することができること
● ”org.scalatest.tagobjects”に標準で用意されているタグが実装されている○ Slow(時間のかかるテスト)
○ Network(ネットワークトラフィックが重いテスト)
○ ChromeBrowser、FirefoxBrowser、InternetExplorer(ブラウザ別のテスト@Selenium)
○ などなど
● 独自タグも実装可能
タグの付け方 - FlatSpec
"This browser" must "is able to hold 100 or more tabs" taggedAs(Slow) in { (1 to 100).foreach.browser.openNewTab
}
it must "download patches over HTTP correctly" taggedAs(Slow, Network) in { val file = browser.download(“http://…”)
file.md5sum should equal “.....”}
fixture
● 複数のテストで共通して利用される値の格納庫
● ScalaTestのテストコードの典型的な設計
例
class Example extends FlatSpec {
def fixture = new { val something = new Something }
“Something” should “be successful” in {
import fixture._
something.value should equal “something”
}
loan fixture
● 名前の通りロジックはloanパターンのそれ
● 受け取ったテストシナリオ関数に、同じデータを引き渡す仕組み
● =異なるテストケースに同じデータ(fixture)を引き渡すことができる
loan fixtureのコード例1
object TypicalData { def establish: DataType = ??? }
class Example extends FlatSpec {
def withTypicalData(testScenario: DataType => Any): Unit = {
val data = TypicalData.establish
testScenario(data)
}
}
loan fixtureのコード例2
“The data” should “contain lines, all of which start with ‘hoge’” in withTypicalData {
data =>
data.lines.foreach(_ should startWith regex “hoge.*”)
}
withFixture
● loanFixture同様、テストケースを引数に受け取る
● テストケース実行前に必ず呼び出される
● つまりFlatSpecを継承(ミックスイン)している自前のテストクラスでwithFixtureをoverrideし、中身にテストの定型部分を記載することができる!
NoArgTest版はテストケースに引数を渡すことができない = 副作用を起こす専門の
ようだ
テストデータに引数を渡したい場合は、OneArgTest版を使う
withFixtureのコード例1
trait Example1 extends FlatSpec {
override def withFixture(test: NoArgTest) = {
setupForExample1
super.withFixture(test) match { // superで親のwithFixtureを呼ぶ=スタック呼出!
case failed: Failed => failed
case other => other
}
withFixtureのコード例2
trait Example2 extends FlatSpec {
override def withFixture(test: NoArgTest) = {
setupForExample2
super.withFixture(test) match {
case failed: Failed => failed
case other => other
}
withFixtureのコード例3(完結)
class Example extends Example1 with Example2 with {
override def withFixture(test: NoArgTest) = {
super.withFixture(test) match {...}
}
“All stacked fixtures” should “be called!” in { … }
}
withFixture + loan-fixture(OneArgTest)
● 『withFixtureをスタックさせつつ、さらに定型的なテストデータをテストケースにぶち
込みたいゾ』という欲求に応える
def withFixture(test: OneArgTest) = {
val fixture = ...
withFixture(test.toNoArgTest(fixture))
}
“This test” should “succeeds” in { fixture => … }
Sharing Tests
● fixtureが投入データの使い回しだったのに対し、テストケースを使い回す方法もあ
る
● 使い回すテストをメソッドに定義
● そんな定型テストメソッドを呼び出すためのシンプルなDSLも提供されている
it should behave like patternalizedTest(arg, …)
参照:http://goo.gl/E99HwP
Selenium用DSL
● WebBrowser.scalaにほとんどの実装が搭載されている
● 正直設計が微妙……○ find(findElement相当)をメソッドチェーンできなかったり
○ ElementがWebBrowserの内部traitなので型で苦労したり
○ 同じくQueryがsealed
○ Elementを継承したTextFieldなどを実装しているのは非常に良い。が、 Elementを具体化するメソッ
ドが遅い&WebBrowser内部でprivateメソッドとして宣言されているため使えない。パターンマッチ
つらい
● ScalaTest3.0で設計変わってるし、1つ目の文句についてはGitHubでプルリク出た
りしてるので、そのうち解決される、かも
● 生のWebDriverを使った方がベター、かも
Unit TestingとIntegration Testingの境界
UnitTesting
Actor内部の処理の中でもActor modelを排除した部分のテスト
つまり並行並列処理のように計算順序に非決定性が内包されない部分のテストの
こと
Unit TestingとIntegration Testingの境界
Integration Testing
並行並列処理、つまり処理に非決定性が含まれるActor modelのテストのこと
要はSynchronous/AsynchronousでUnit/Integrationを分けよう、ということ
TestActorRef
通常ActorRefは参照先のActorを外部から隠蔽している
TestActorRefはテストを目的として、外部からActorへのアクセスを許可してい
るクラス
val testActorRef = TestActorRef[OneActor]
val actualActor = testActorRef.underlyingActor
メッセージ検証
val actorRef = TestActorRef(new OneActor)
val answer = actorRef ? PutNumber99
val Success(result: Int) = answer.value.get
result should be (99)
FSMのテスト
TestFSMRefでFSM使うことで、Actorの状態を読み書きできる
val testFSMRef = TestFSMRef(new OneFsmActor)
testFSMRef.stateName should equal(InitialState)
testFSMRef.stateData should equal("")
testFSMRef ! "next"
testFSMRef.stateName should equal(NextState)
testFSMRef.stateData should equal("next")
testFSMRef.setState(stateName = InitialState) // stateData, timeout,
stopReasonなども書き換え可能
(補足)TestKit
Actor用(Integration Testingの領域含む)のテスト用の機能が定義されている
クラス
within (1.second) { // ブロック内が1秒以内に完了するかチェック
fsmRef ! direction("do hoge")
// awaitしてあげる
fsmRef.stateName should equal ("do hoge")
// 期待したメッセージが届いたかチェック
}
(補足)TestActors
テストでよく使うだろう『やまびこ』と『リダイレクト』アクターは、TestActors
内に予め用意されている
EchoActor <- echoActorPropsで生成済み
ForwardActor <- forwardActorPropsにリダイレクト先を引数として渡せば生成
される