Akka Unit Testing

34
Akka for Reactive Programming - Akka Unit Testing Masashi Kawaguchi / Jangsa Cho

Transcript of Akka Unit Testing

Akka for Reactive Programming- Akka Unit Testing

Masashi Kawaguchi / Jangsa Cho

メニュー

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フレームワーク利用のシンタックスシュガー

テストの表記スタイル

二種類に大別される

1. Spec(s)系2. JUnit系

http://goo.gl/x7n8Uh

テストコード用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を使った方がベター、かも

Akka TestKit

Akkaで構築されたシステムを効率的にテストするためのモジュール

Unit Testing、Integration Testingそれぞれのレベルでのテストに対応

Unit TestingとIntegration Testingの境界

UnitTesting

Actor内部の処理の中でもActor modelを排除した部分のテスト

つまり並行並列処理のように計算順序に非決定性が内包されない部分のテストの

こと

Unit TestingとIntegration Testingの境界

Integration Testing

並行並列処理、つまり処理に非決定性が含まれるActor modelのテストのこと

要はSynchronous/AsynchronousでUnit/Integrationを分けよう、ということ

Actor内部のテスト

Actorひとつ試験する場合にも、

単体レベルで検証すべき部分、結合レベルで検証すべき部分が混在する

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にリダイレクト先を引数として渡せば生成

される

(補足)IntegrationTesting = Asynchronous

val echoer = ActorSystem("TestHoge").actorOf(TestActors.echoActorProps)

echoer ! "hoge"

expectMsg("hoge") // TestKit内、Actor model用のassert