プログラム書くの遅い人、 仕様が整理できてないか、 IDE を使い慣れてないか、言語の標準 API を覚えてないかのいずれかなので、勉強し直してきてください
— 引きこもり持田 (@mike_neck) 2020年12月26日
Kotlin を使ったプログラムを書く場合には以下の要素によってプログラムを書く速さが決まってくる
続きを読むプログラム書くの遅い人、 仕様が整理できてないか、 IDE を使い慣れてないか、言語の標準 API を覚えてないかのいずれかなので、勉強し直してきてください
— 引きこもり持田 (@mike_neck) 2020年12月26日
Kotlin を使ったプログラムを書く場合には以下の要素によってプログラムを書く速さが決まってくる
続きを読むやんくさんに頼まれたので、誰かが書くだろうと思って待ってたけど誰も書かない Gradle の kotlin-dsl
プラグインについて書きます。
@mike_neck
— やんく (@yy_yank) 2020年12月12日
mikeさんアドベントカレンダー書くネタあったりしませんか?👉👈https://t.co/ZDwmyBpbC0
まず、先日(2020/12/11)開催された JJUG ナイトセミナーにて、時間がないのでほぼ飛ばしてしまった、こちらの話題について解説します。
build.gradle
(build.gradle
) の中で if
文(Kotlin だと式ですね)とか for
文(こっちは式だったっけ?)を書くと、宣言的なコード(例えば依存ライブラリーの宣言など)と命令的なコードが混在してしまいます。そのため、ビルドスクリプトを読むために頭の切り替えが必要になるのでメンテナンス性が下がるというのが理由です。したがって、命令的なコードを書いている部分はすべてプラグインにして、 buildSrc
プロジェクトに移動するのがよいと公式のドキュメントに書いてあります。
しかし、これは素直におすすめできないところがあって、プラグインを書くためには少しだけプラグインの仕組みの学習コストがかかります(まあ、大したコストではありませんが…) 。そこで今持っている build.gradle
(build.gradle.kts
) の 知識でプラグインを無理なく書ける仕組みを使うのがよいでしょう。
プリコンパイルド・スクリプト・プラグイン(Precompiled script plugins) というのがその仕組です。
具体的には以前このブログで紹介した groovy-gradle-plugin
が Groovy の場合のプラグインです。ここでは詳しく解説しないので、以下のエントリーを読んでください。
これの Kotlin 版が kotlin-dsl
プラグインです。(この言い方は語弊があって、先に kotlin-dsl
があって、その後に Groovy 版ができたらしい)
なお、ググラビリティが悪いので、このプラグインの名前はどうにかしてほしい
kotlin-dsl
の書き方buildSrc/build.gradle.kts
ファイルに以下のように書きます。 repositories {}
ブロックを忘れると kotlin-dsl
をビルドするための Kotlin をダウンロードできなくなってエラーが発生します
plugins { `kotlin-dsl` } repositories { jcenter() }
buildSrc/src/main/kotlin
の下に任意の名前で .gradle.kts
ファイルを作成します。ここではサンプルのため、 example.gradle.kts
というファイル名にします。なお、内容的には先の
Java toolchains のエントリーの build.gradle
と同じ様な内容にしています
plugins { `java` } repositories { mavenCentral() } dependencies { compileOnly("org.jetbrains:annotations:19.0.0") implementation("org.slf4j:slf4j-api:1.7.30") testImplementation("org.junit.jupiter:junit-jupiter:5.7.0") testImplementation("org.assertj:assertj-core:3.18.1") } internal val ss = sourceSets internal val testSourceSets = ss.getByName("test") @Suppress("UnstableApiUsage") internal val javaToolChains = extensions.getByType<JavaToolchainService>() @Suppress("UnstableApiUsage") internal val javaPluginExt = extensions.getByType<JavaPluginExtension>() javaPluginExt.toolchain { @Suppress("UnstableApiUsage") languageVersion.convention(JavaLanguageVersion.of(8)) @Suppress("UnstableApiUsage") vendor.convention(JvmVendorSpec.AZUL) @Suppress("UnstableApiUsage") implementation.convention(JvmImplementation.VENDOR_SPECIFIC) } open class ProjectTestConfig { @Suppress("UnstableApiUsage") // 指定されたバージョンの Java でテストを実行するタスクを登録します fun testOnJavaVersion(vararg javaVersions: Int): Map<Int, TaskProvider<Test>> = javaVersions.map { javaVer -> javaVer to tasks.register<Test>("testJava${javaVer}") { group = "verification" description = "runs test on Java $javaVer" classpath = testSourceSets.runtimeClasspath testClassesDirs = testSourceSets.output.classesDirs useJUnitPlatform() javaLauncher.convention(javaToolChains.launcherFor { languageVersion.convention(JavaLanguageVersion.of(javaVer)) }) } }.onEach { pair -> tasks.named("check").configure { dependsOn(pair.second) } }.fold(mapOf()) { acc, pair -> acc + pair } } extensions.add<ProjectTestConfig>("testConfig", ProjectTestConfig())
なお、このスクリプトですが、書いている途中でコンパイルエラーが発生すると補完が効かなくなるので、気合で乗り越えていく必要があります。 これが僕が Kotlin DSL をあまり好まない理由の一つです。
最後に、ルートディレクトリーつまりメインプロジェクトの build.gradle.kts
です。ここで、先程の .gradle.kts
ファイルの拡張子を抜いた部分(ここまでの例だと example.gradle.kts
→ example
) をプラグインとして指定します。
plugins { `example` } // テストは Java 11 と 15 でも実行する testConfig.testOnJavaVersion(11, 15)
かなりシンプル(というより、なにもない)になりました。
では、 Java 8 以外で実行すると落ちるようなテストを書いて実行してみます
class Version { @Test void show() { String javaHome = System.getProperty("java.home"); assertThat(javaHome).contains("jre"); } }
というわけで、 kotlin-dsl
を使うとビルドスクリプトがシンプルになったと思います。また、 groovy での記事でも書いたように複数のサブプロジェクトに対してプロジェクト横断的に同一な記述が必要になるケース(例えば、同じ Kotlin のバージョンと Linter と フォーマッターを使いたい)で、 kotlin-dsl
で作ったプラグインをサブプロジェクトで利用できます。
kotlin-dsl
のおさらいbuild.gradle.kts
から命令的なコードを取り除いて、 buildSrc
プロジェクトに命令的なコードを記述するbuildSrc
にプラグインを書く場合に kotlin-dsl
を使うと .gradle.kts
ファイルを作成する以上のような形でビルドスクリプトをシンプルにできます。
テストフレームワーク ktcheck の宣伝も兼ねて発表してきた
資料はこれ
www.slideshare.net
現在勤めている会社のテストのほぼすべては kotlintest(現 kotest) で記述されているのですが、資料にあるようなテストデータ(変数)の状態がうまく扱えないこともあり、どの順序で実行されているかを調べていたりしました。
変数の状態良くわからん問題は発表中の 反応 を見ても共感を得られていたっぽいので、大体つまずく場所は一緒だということも認識できました。
「これひどいよな」みたいに愚痴を言おうとしましたが、これだけ大きなテストフレームワークを開発できるのだから何か回避策があるに違いないとドキュメントを読み直して IsolationMode
のドキュメントにたどり着いてテストを書き直したいと思ったりしました。ドキュメント読み込み大事。
ktcheck はまだ作ったばかりでフィードバックがほしいところなので、ぜひ使ってもらいたいなと思っています|ω・`)チラッ
表題の通り、テストフレームワークをリリースしました。
名前は ktcheck です。
JUnit Platform 上で動かせますが、普通のメインプログラムの中でも使えます。
gradle の場合、次の記述を dependencies
ブロックに記述します。
dependencies {
testImplementation("run.ktcheck:ktcheck:v0.1.0")
}
object
を作って、 KtCheck
インターフェースを実装させて、 Given.When.Then
で生成されるオブジェクトに移譲します。
object IntPlusTest: KtCheck by Given("Int の値 1 に", { 1 }) .When("Int の値 3 を足すと", { int -> int + 3 }) .Then("4 になる", {_, result -> result shouldBe 4})
条件を Given
に記述し、操作を When
に記述して、 Then
にアサーションを記述するのを強制するようなフレームワークになっています。
あとはテストの実行ですが、 IntelliJ では ktcheck のテストを検出できませんので、 gradle のテストタスクを起動します
テストが実行されて、結果も表示されます。他の gwh スタイルのテストフレームワークよりも明確にテストの名前が表示されるので落ちたテストについてどのようなテストであるかがわかりやすくなっています(kotest のいやなところをこちらは解決した)。
もう少し、凝った使い方もできますが、とりあえず省略です。
レポジトリーはこちらです
あと、ロゴを作ってくれる人いたら紹介してください。