danger-kotlinをやっていこう
DangerfileをKotlin scriptで書けるというので試してみている。今の時点ではver. 0.5.1でWIPとのこと。
Github Actionsで実際に動かしてみているのだが、これまでDangerで使っていたようなプラグインが使えるようにはなっていないようで、今のところ簡単ではない。それでもkotlin力(ちから)に自信があれば自分でプラグインを書いてもよいと思う。
DangerfileをKotlin scriptで書くにあたり、コードの自動補完が効くようにしたい。開発環境がMacOSやLinuxの場合はREADMEに書いてある通りに進めればいいのであろうが、私はWindowsで作業しているので少々難儀した。
READMEにはまず最初にdanger-kotlinをマシンにインストールせよと書いてあるので、Makefileを読みビルドの手順をWindowsに置き換えて実行しようとしたのだが、Windowsだとビルドが通らないようになっていた。その後少々考えて、ダメ元でWSL2の上でJarをビルドし、これに対してパスを通したところ、自動補完が効くようになった*1。
あとはKotlin scriptの設定がどこにあるか分からなかった。Android Studio 4.0では設定のOther Settings > Kotlin compilerの下の方に目当てのものがある。
プラグインをどうにかしたい。gradleでjrubyやgemを使えるようにするプラグインはあるので、Kotlin scriptでも何とかして似たようなことができないだろうか。無職になり有り余る時間ができてしまったので挑戦してみてもいいかもしれない。
*1:インタフェースはちゃんと見えているから自動補完だけなら大丈夫ということだろうか
重い腰を上げてテストのないクラスのテストを書くときまず最初にやること
テスト書くの後回しにしちゃったなーでもこの関数のテスト書かなきゃ…みたいなとき、まずどうするか。大事なことは最初のハードルを下げたり、ステップを小刻みにすること。
とりあえず最初はテスト対象のインスタンスを生成してそれがNonNullであるというテストを書く。つまりインスタンスを正しく作れるかというテストで、必要なことはコンストラクタに渡すインスタンスをどうやって作るかということだけである。だけなのだが、面倒なことがいくつかある。例えば、モックを作って渡すなら初期化時に呼ばれる関数の戻り値を設定しなければならない。そのほかにも、AndroidならContextなどプラットフォームのクラスを中で使っている場合はAndroidJUnit4をテストランナーとして設定する必要がある。うっかりLog.d()が入り込んでいたりするとTimberに置き換えるといったような別の作業も発生するかもしれない。
最初にやらなきゃいけないことが実はたくさんあるので、いきなり本題のテストを書き始めようとしてもなかなか進まずイライラすることになる。また、これらの作業を経ることによってimportがいい感じに温まっているという副産物もある*1。
最初のテストが書けたら、次は書こうと思っていた関数のテストのうち、もっとも単純なケースのテストをとりあえず2・3個書いてみる。TDDでレッドとグリーンの間を行ったり来たりしてもよい。きれいなコードを書く必要はなく、コピペしてテストの関数名と渡すパラメータを少し変えたようなものでよい。いくつか書いていくと、自分が本当はどんなことをこのテストで確認したいと思っているのかということや、テストに共通で登場する前提条件や処理群といったものが明らかになる。特に共通する処理の最たるものというのはほかでもなく、最初に書いたNonNullテストの中の初期化処理である。これをひとまずはsetupのための関数として使えるように、TestアノテーションをBeforeアノテーションに置き換え、生成したインスタンスを各テストケースからアクセスできるようプロパティにセットする*2。
あとはコピペで作ったテストから重複するコードを取り除いたり、いっそのことパラメタライズドテストに書き換えてみたり、テストクラスが大きくなったら前提条件ごとに内部クラスに分割したり、どの内部クラスでも使うRuleをまとめて使えるようにしたRuleを作ったりしながらテストを育てていけばよい。
まとめ
こんなブログを読むよりもレガシーコード改善ガイドとかを買って読んだ方が明らかに役に立つ。
RobolectricのSQLiteの実装がPRAGMA defer_foreign_keysをサポートしていなかった
androidTestで使うDBのデータをセットアップするという記事を以前書いた。
今回はこれをJUnitテストでも使えるようにしようとしてはまったポイント。
robolectricで使ってるsqliteの内部実装がdefer_foreign_keysに対応してないっぽくてどうしたもんかとなっている
— まつだあきひと (@akihito104) 2020年5月21日
今回の前提:
testImplementation 'org.robolectric:robolectric:4.3'
言いたいことはタイトルですべて言っているので、どう対応したかということについて説明する。といってもそんなに難しいことはなく、単に PRAGMA defer_foreign_keys;
を実行して戻り値があるかどうかを確認するだけである。
val isDeferForeignKeysSupported = supportSqliteDatabase.query("PRAGMA defer_foreign_keys;").use { cursor -> cursor.count == 1 } if (!isDeferForeignKeysSupported) supportSqliteDatabase.execSQL("PRAGMA foreign_keys = false;") // これは使えるらしい supportSqliteDatabase.beginTransaction() if (isDeferForeignKeysSupported) supportSqliteDatabase.execSQL("PRAGMA defer_foreign_keys = true;") ...
これでandroidTestでもJUnitTestでもDBにデータをロードしてテストできる。もっといいやり方があれば教えてください。
[2020/12/22 追記] robolectric 4.5-alpha-1以上なら大丈夫かもしれない akihito104.hatenablog.com