visible true

技術的なメモを書く

KotlinTestのBehaviorSpecでGivenの単位でBeforeしたい

KotlinTestいいんですよね。 AndroidじゃなくてSpringBootで使ってるんですが。 Androidでも使えるかな?どうかな?まだ無理っぽい

beforeSpecはSpec開始時に一度だけしか実行されない

KotlinTestのSpec群はbeforeなんとか、afterなんとか系の関数を持っていて、オーバーライドして使うんですが、例えばbeforeSpec(spec: Spec)なんかだと、 Spec開始時に一度だけしか実行されないので、Given単位でDBの状態変えたいなって時に使えないんですよね。

beforeTestでisTopLevelを見る

別のタイミングとしてbeforeTest(testCase: TestCase)というのがあるんですが、こっちはGiven, When, Thenの全部で呼ばれる。これだとGivenでDB作ってもWhen行く時には消えてしまう。 beforeGivenとかないんかなと思ったけどないらしい。

beforeTestにはTestCaseが渡ってくる。TestCaseにいろんな情報があるので、これを使って判定すると良さそうってことでいろいろガチャガチャやってたら、どうもGivenはisTopLevelがtrueになるらしいということがわかった。

ということでbeforeTestでこんな感じでやると良さそう。

class TodoResourceSpec(
  val mockMvc: MockMvc,
  val dataSource: DataSource
) : BehaviorSpec() {

  override fun beforeTest(testCase: TestCase) {
    if (testCase.isTopLevel()) { // Givenの時だけtrueになる
      dbSetup(dataSource) {
        truncate("todo")
      }.launch()
    }
  }

  init {
    Given("I have empty todo list") { // ここと
       When("get todo list") {
         val result = mockMvc.perform(
             get("/api/todo")
               .contentType(MediaType.APPLICATION_JSON)
         )
         Then("should return empty list") {
           result.andExpect(status().isOk)
             .andExpect(content().json("""[]"""))
         }
      }
    }
    Given("I have a one todo") { // ここでtruncateが走る
        // 省略
    }
  } 
}

おわり

BehaviorSpecは抽象クラスなので、継承してbeforeGivenとか関数生やしてもいいかもしれないと思った。 TestCase#nameにはWhenとかThenとか文字列で書いてあるのでそれを見てbeforeWhenとかもできそう。 もっと違う方法あるきもする

Safe Args PluginでParcelable Arrayを使いたい時は型の末尾に[]って書けばいいらしい

ObjectArrayTypeとputParcelableArray

実装を読んでみるとObjectArrayTypeという型があり、putParcelableArraygetParcelableArrayマッピングしてるぽいことがわかる。

https://android.googlesource.com/platform/frameworks/support/+/9d3737306a6092c8ce8a98163b3a8070f0dcab7e/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/Types.kt#152

型の末尾の[]をチェック

https://android.googlesource.com/platform/frameworks/support/+/9d3737306a6092c8ce8a98163b3a8070f0dcab7e/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/Types.kt#47

あとここで、型の末尾に[]があるかチェックしている。

なのでこんな感じに書けばよい。

<argument
  android:name="authors"
  app:argType="jp.dip.sys1.aozora.models.AuthorCard[]" />

AuthorCardはもちろんParcelableでなければならない。

おわりに

うしさんありがとう。

Pass data between destinations  |  Android Developersには、

You can check Array to indicate that the argument should be an array of the selected Type value.

と書いてあるので、いずれAndroid Studio上でもarrayかどうかのチェックをUIで設定できるようになるはず(canaryだと動いてそう)。

リリース前レポート(Firebase Test Lab)で動作しているか判定する

リリース前レポート助かりますよね。Firebase Test Labベースでいろいろ根掘り葉掘りやってくれて最近ではフィードバックもいろいろ充実していて良い感じです

f:id:sys1yagi:20190218164210p:plain

リリース前レポートで動いてるのか判定したい

リリース前レポートいいんですけどロボットがガチャガチャ適当にやるので、あんまり掘ってほしくないルートに行ったりもするんですよ。

たとえば個人アプリでFirebase AuthでGoogleログインをサポートしてたりするんですが、Googleアカウント新規作成みたいなところまでいっちゃうことがよくあって最終的にクラッシュ扱いでレポートが上がってきたりします。

f:id:sys1yagi:20190218164908p:plain:w250
おい

また、広告表示なんかも抑えたいですね。実際に機械によるクリックを防止しろとドキュメントに書いてあったりします。リンクの先はなんか広告側で頑張ってブロックしてね的なドキュメントがあるんですが実施するのは難しい感じで厳しい感じでした。

f:id:sys1yagi:20190218165256p:plain

firebase.test.labフラグを見る

なんとかできないかな〜とつぶやいていたら2年前に自分で実装していたらしい。

完全に忘れてました。ということでFirebase Test Lab and Android Studio  |  FirebaseにあるようにFirebase Test Labの環境で動いているかを次のコードで判定できます。

fun isTestLab(context: Context): Boolean =
    Settings.System.getString(context.contentResolver, "firebase.test.lab") == "true"

やったね

おわりに

次は忘れないように書きました。ちばさんありがとう!

java.lang.ClassCastException: com.sun.tools.javac.code.Type$1 cannot be cast to javax.lang.model.type.DeclaredTypeが出たらテストで使うコードがエラーになってるぽい

AndroidX対応を進めていたら自分でつくって自分で使っているFragment生成周りが便利になるhttps://github.com/sys1yagi/fragment-creatorが適切にAndroidX対応できてないぽいことがわかり、いろいろと更新してたらテストで次のエラーがでてこけるようになってしまった。

java.lang.RuntimeException: java.lang.ClassCastException: com.sun.tools.javac.code.Type$1 cannot be cast to javax.lang.model.type.DeclaredType

これはアノテーションプロセッサで型チェックをしている場所で起こる。

https://github.com/sys1yagi/fragment-creator/blob/master/processor/src/main/java/com/sys1yagi/fragmentcreator/model/EnvParser.java#L49

compile-testingで読み込むJavaFileObjectがコンパイルできない場合に起こる

色々調べてみると、https://github.com/google/compile-testingで読み込むテスト用のJavaファイルをロードする時、Javaファイルで使ってるクラスを解決できない場合起こるぽいという事がわかった。設定をいじったから色々変になったのかな〜と思っていたのだけど、テストで使うコードのクラスパスが通ってない結果起こるエラーだとは...。

ということでテストで使うコード(たとえば https://github.com/sys1yagi/fragment-creator/blob/master/processor/src/test/assets/TypeSerializerFragment.java )を見るとandroidx.fragment.app.Fragmentを使うようになっていた。なるほどテストのクラスパスにandroidx.fragment:fragmentを通さないといけないらしい。

先人のコードを参考にする

JSR269周りでAndroidX対応を始めたタイミングとしては大分後発だと思うのできっと先人達がいい感じにしてるに違いないということでいくつかのリポジトリを渡り歩いた。

めちゃくちゃドンピシャだったのがPermissionsDispatcherで、ほぼ全体的に参考にさせてもらった。わいわい。 Replace deprecated "com.google.android" dependency with android.jar · permissions-dispatcher/PermissionsDispatcher@a5e0cac · GitHub

参考になったのは大きく2点

おわりに

AndroidXをサポートした Fragment Creator 2.0.0でました。

Kotlin Coroutine 1.1.0-alpha Change log 全部読む

本エントリは Kotlin Advent Calendar 2018 の記事です。

Kotlin Coroutine 1.1.0-alphaがリリースされましたね。「kotlinx.coroutines/CHANGES.md at master · Kotlin/kotlinx.coroutines · GitHub」Change logを読むことの重要さを思い知ったので全部読みます。

Major improvements in coroutines testing and debugging

コルーチンのテストとデバッグの改善のために新しいモジュールが2つ増えました。

kotlinx-coroutines-debugの追加

ひとつめはkotlinx-coroutines-debugkotlinx.coroutines/core/kotlinx-coroutines-debug at master · Kotlin/kotlinx.coroutines · GitHub

コルーチンの追跡とトレースができるようになります。ByteBuddyを使っているらしい。Androidで使えるかな..? dependenciesをtestImplementation に追加してねというのも気になる。

kotlinx-coroutines-testの追加

もう一つはkotlinx-coroutines-testkotlinx.coroutines/core/kotlinx-coroutines-test at master · Kotlin/kotlinx.coroutines · GitHub

Dispatchers.setMain関数などを提供してくれるらしい!これは助かる。

スタックトレースの改善

System.setProperty("kotlinx.coroutines.debug", "") ってセットしておくと、dispatcherを跨いでもスタックトレースで追えるようになる。これは助かる。

Basic stacktrace augmentation · Issue #493 · Kotlin/kotlinx.coroutines · GitHub

その他の改善

MainScopeファクトリとCoroutineScope.cancel関数を追加

https://github.com/Kotlin/kotlinx.coroutines/issues/829

MainScopeファクトリの中身がCoroutineScope(Dispatchers.Main + SupervisorJob()) で、root scopeではSupervisorJob使っていこうなってことなのかな〜うーんという感じ。CoroutineScope.cancelは便利そう。

resumeWithExceptioncancelのCancellableContinuationの競合が解決された。キャンセル中の例外は例外ハンドラに伝搬しなくなった。

特に感想なし

Dispatchers.DefaultJVM上のCPU消費量を削減

https://github.com/Kotlin/kotlinx.coroutines/issues/840

CoroutineSchedulerが処理がない時に余計に計算してたらしい。改善されてよかった

初期化されてないDispatcherをより素早く診断して検出

https://github.com/Kotlin/kotlinx.coroutines/issues/880

Dispatchers.Main使ってるのにandroid dependencies入れてないみたいな時にハングするケースがあったらしい。直ってよかった。普通に使ってる分にはあんまり関係なさそう。

Conflated channelが線形化可能になった

どういうこと〜?

コルーチンの結果の型がDisposableHandle だったときに起こる問題を修正

https://github.com/Kotlin/kotlinx.coroutines/issues/835

ハングっちゃうらしい。普通に使ってる分には関係なさそう。

Dispatchers. JavaFx の初期化のバグを修正

https://github.com/Kotlin/kotlinx.coroutines/issues/816

withTimeout関数のバグを修正

https://github.com/Kotlin/kotlinx.coroutines/issues/870

タイムアウトを負の数にすると、TimeoutCancellationExceptionじゃなくてCancellationExceptionが飛んでしまう問題を修正したらしい

Kotlin/Native でシングルスレッドワーカーをサポート

Kotlin/Nativeでもコルーチンが便利に使えるらしい。

JavascriptDispatchers.Defaultでjsdomをサポートした

あんまりよくわかってない

rxFlowableの型パラメータがAny で制限された

あんまりよくわかってない

Guava 27をサポート

kotlinx-coroutines-guava

コルーチンをプログレッシブモードでビルドするようになった

Kotlin 1.2.50とかで入ったモードっぽいけど詳細はわからない

ドキュメントをいろいろ修正

わいわい

感想

意外としれっといろいろ入るんだな〜。おもしろい。

Androidと非同期処理 とCoroutine1.0.0

Google Play App Dojoで話してきました。Google Play App Dojoは非公開イベントなので全体的な内容は言えないんですが、どの発表もおもしろい & 濃いので、もし参加のチャンスがあったらぜひ行ったほうがいいな〜と思いました。

Androidと非同期処理 とCoroutine1.0.0

資料はコチラ

話した事

f:id:sys1yagi:20181219102256p:plain

詳細は資料を見て下さい。

その他リンクなど

Android Suspenders

Android Dev Summit '18のコルーチンのセッションです。資料読んだあと見るとわかりが深まると思います。Androidと非同期処理 とCoroutine1.0.0 では触れられなかったところ(Android APIをコルーチン化する、複数回値を返却するケースなどなど)が結構あるのでぜひ見て下さい。 www.youtube.com

資料内でリンクしてるやつ

おわりに

SupervisorJobとsupervisorScope関数についてはAndroidでは不要だろうなと思っているけどもう少し調べたらブログ書こうかなと思います。だいじなことなので二回書きますがGoogle Play App Dojoどの発表もおもしろい & 濃いので、もし参加のチャンスがあったらぜひ行ったほうがいいな〜と思いました。

Kotlin Coroutine 0.26.0 Structured concurrencyってなんなの

Structured concurrency – Roman Elizarov – Mediumでは、Kotlin coroutine 0.26.0でCoroutineScopeを導入した経緯などが書かれているんですが、タイトルのStructured concurrencyってのが結局なんなのか本文中にはあんまり出てこないんですよね。

んで、CoroutineScopeの動作の理解に精一杯で、coroutineScope関数がなんなのか、どういう時に使うべきなのかよくわからないし、ましてやsupervisorScope関数なんて言われた日には意味が不明なわけです。 とりあえずlaunch関数内でasync関数呼び出したら駄目なんだな〜ふ〜んなんで?

Notes on structured concurrency, or: Go statement considered harmfulを頑張って読む

Structured concurrency – Roman Elizarov – MediumのFurther readingにNotes on structured concurrency, or: Go statement considered harmful — njs blogを読むことを強く勧める!と書いています。パッと開くと超長いので心が折れかけるんですが読んでみたら面白かったのでメモします。

並行性の構造化

Structured concurrencyは概ね 並行性の構造化 って訳すとよさそうです。 構造化プログラミングはgotoを駆逐するために考えられたものですが、並行性の構造化は並行処理におけるgoto的なものを駆逐するための議論みたいです。 構造化プログラミングは順次, 反復, 分岐の構造を組み合わせてプログラム書くことでgotoは無くせるしむしろいい感じになるぜって話ですね。

並行性の中のgoto

エントリではgo statement(goroutineの起動)はgotoみたいなもんじゃ!って言ってます。go statementに限らず並行処理を起動するものはどれも同じみたいです。 たとえばtry-with-resources(これはJava)の中でスレッドを起動してその中でリソースにアクセスしたらどうなるか?ぶっ壊れます。 gotoじゃねえか!ということだそうです。でも確かにわかる気もする。

Trioの例

https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-a-structured-replacement-for-go-statements では、並行性についても構造化プログラミングと同じように並行性の構造のモデルを定義して取り入れるといいんじゃないか的な話と実際にpythontrioというライブラリの說明が始まります。

とりあえず図としてはこれがわかりやすいんじゃないかと思います。ほかのパターンもあるのかはちょっと分からないですがCoroutineScopeを理解するには十分かなと思います。

f:id:sys1yagi:20181212220527p:plain:w300

fork/joinに似てますけど実際に影響を受けてるらしいです。つまりどっから並行性が始まってどこで終わるんや、ってことを明示的に書けってことですね。これCoroutineScopeですね。こういう感じ。

suspend fun startParallel() = coroutineScope { // 並行性の分岐の根本
    // 3並行に分かれる
    val a = async { requestA() }
    val b = async { requestB() }
    val c = async { requestC() }

    // 合流する
    a.await() + b.await() + c.await()
}

タイミングがずれるケースとか複雑に分岐するケースとかもあるとおもうんですが、fork/joinの根本と合流がcoroutineScopeで適切に切られていればOKってことですね〜

例外の伝搬とCoroutineScope

例外の伝搬についてもfork/joinの図がわかりやすいですね。分岐の何処かが死んだら全体が即座に死ぬ。並行実行の単位がスコープってことですかね〜。 launch関数内でスコープを切らずにasync関数呼び出したら駄目なのは、async関数が並行性の分岐を表すからで、スコープを切らない場合はforkの根本がlaunch関数の親のスコープになるから、と說明できそうです(図を書いてみたもののカチッとしなかったので省略)。

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
// asyncがエラーになるとscopeがキャンセルされる
scope.launch {
  val a = async { requestA() }.await()
}

終わりに

とりあえず雑ですがメモです。大分わかってきました。まだ理解できてないとこもありそうですが.. ざっくりしか読んでないからまた何度か読みます。 Notes on structured concurrency, or: Go statement considered harmful — njs blog