mike-neckのブログ

Java or Groovy or Swift or Golang

Gradle JUnit5 Starter Pluginをリリースした

f:id:mike_neck:20171116211230p:plain

JUnit5 がリリースされてだいぶたちますが、そろそろ移行終わりましたか?

ちなみに僕が勤務している会社ではJavaのバージョンがあわないので、JUnit4のままです。

ところで、JUnit5を使う時、わりと面倒くさい設定をいろいろと書かなければならず、導入するための最初のハードルが高いのではないかと思います。

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.2'
  }
}
apply plugin: 'java'
apply plugin: 'org.junit.platform.gradle.plugin'
repositories {
  mavenCentral()
}
dependencies {
  testCompile 'org.junit.jupiter:junit-jupiter-api:5.0.2'
  testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.0.2'
}

JUnit4であれば、 testCompile 'junit:junit:4.12' だけで良かったんですけどね…


ニーズが有るようですし、暇だったので、もう少し記述量の減らせるGradleプラグインを作ってみました。

Gradle - Plugin: org.mikeneck.junit.starter.normal

このプラグインを使えば、上のわりと多めの記述が、こんな感じで省略できます。

plugins {
  id "org.mikeneck.junit.starter.normal" version "5.0.2"
}

なお、これの Spek 用のプラグインも同時にリリースしています。(SpekはエンジンにJUnit5を利用しているので、JUnit5に引きずられて記述量が多くなってしまう)

plugins {
  id("org.mikeneck.junit.starter.spek") version "1.1.5"
}

おわり

kotlinでgradle のスクリプトを書く時に気をつけたい点

f:id:mike_neck:20171113232305p:plain

Gradleは元々Groovyで書かれてたツールで、バージョン2の頃にほぼすべてをJavaに置き換えられたという経緯があるのですが、未だに古い一部の実装ではGroovyでスクリプトを書くことを前提にしている箇所があります(と断言していいかどうかは自信がない)。

maven プラグインuploadArchives まわりもGroovyで書くことを前提としているフシがあり、 MavenDeployer -> MavenPom#project の後のブロックは delegate オブジェクトが groovy.lang.GroovyObject というまさに groovy なDSLになっている

先日、mavenにリリースするライブラリーのdslをkotlinで書いていたが、この部分のdslを最初次のように書いていた。入れ子構造になっている部分を書き始めようとした時に、どのように書くのかわからず、手探りで書いていた。

mavenDeployer {
  pom.project {
    invoke("name", "library-name")
    invoke("packaging", "jar")
    invoke("scm", mapOf(
      "connection" to "scm:git:https://github.com/mike-neck/repository-name",
      "url" to "https://github.com/mike-neck/repository-name"
    ))
  }
}

しかし、この部分、おそらくgroovyの MarkupBuilder を使っていることは想像がついたため、このようなDSLだと次のような望まないpom.xmlが生成されるように思われた。

<name>librry-name</name>
<scm connection="scm:git:https://github.com/mike-neck/repository-name" url="https://github.com/mike-neck/repository-name"/>

というわけで、1〜2時間悩んでいたのだが、kotlin-dslのサンプルにあたってみたところ、すでに解決策が提示されていた。

github.com

GroovyObject を扱う場合は withGroovyBuilder(GroovyBuilderScope.() -> T) という拡張関数を使えば解決できるそうだ。

したがって、次のような dslを書けば、valid な pom.xml が出力される

mavenDeployer {
  pom.project {
    withGroovyBuilder {
      "name" ("library-name")
      "packaging" ("jar")
      "scm" {
        "connection"("scm:git:https://github.com/mike-neck/repository-name")
        "url"("https://github.com/mike-neck/repository-name")
      }
    }
  }
}

Gradle Kotlin DSL を用いたプロジェクトでmavenにpublishするときの注意

f:id:mike_neck:20171113232305p:plain

gradle kotlin-dsl のサンプル「hello-kotlin」を真似て、kotlin stdlib のdependencyを記述すると、invalidなpomが生成されてしまう。

dependencies {
    api(kotlin("stdlib-jre8"))
}

生成されるpom.xml

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jre8</artifactId>
</dependency>

version がないので、この pom.xml は invalid。


正しくは DependencyHandler.kotlin extension function の引数 version もちゃんと指定する

dependencies {
    api(kotlin("stdlib-jre8", "1.1.51"))
}

または、

dependencies {
    api(kotlin(module = "stdlib-jre8", version = "1.1.51"))
}

これにより、valid な pom.xml が生成される

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jre8</artifactId>
    <version>1.1.51</version>
</dependency>

昨日書いたダメなコード

毎日残念なコード書いてるので続けてみる

前提

  • Java7(今日は関係ない)

やりたいこと

  • 指定した範囲のランダムな long の値がほしい

書いたコード

例 : 上限値(long かつ max > 0)を受け取ってランダムな値を返す

final Random random = new Random();

long randomLong(final long max) {
  while(true) {
    final long generated = random.nextLong();
    if (0 <= generated && generated < max) {
      return generated;
    }
  }
}

ダメなところ

max の値が小さいと時間がかかりすぎるというかほぼ無理ゲー

どう書くのがよかったか

ThreadLocalRandom を使う

long randomLong(final long max) {
  return ThreadLocalRandom.current().nextLong(max);
}

ThreadLocalRandom では seed を設定できないため(setSeedUnsupportedOperationException を投げる)、 seed を使いたい場合は元の Random を使うしかないので、 BigInteger を強引に用いる

final Random random = ...;
long randomLong(final long max) {
  final int bitLength = BigInteger.valueOf(max).bitLength();
  while(true) {
    final long generated = new BigInteger(bitLength, random).longValue();
    if (generated < max) {
      return generated;
    }
  }
}

実は ThreadLocalRandom はつい今しがた知ったので、実際は下の方法を使った