MPのご利用は計画的に

だいたい自分用のメモ

batファイルをMakefileのようにサブルーチン指定で実行する

概要

自分の開発環境はMacなのでmakeが初期状態から実行できるのですが、 チーム開発を行うときにWindowsな人がいることもありますよね。

MacでもWindowsでも環境構築をワンライナーで終わらせたいと思ったときに、 Makefileと同じような書き方をしつつ、コマンドプロンプトでどうにかできないのか調べたメモです。

環境

TL;DR

  • batファイルにはサブルーチンという関数に近い機能がある
  • サブルーチンは:ラベル名で作る
  • ファイルの先頭でサブルーチンを呼ぶためにgotoを置く
  • call makefile.bat <label-name>

どうすればいいか

batファイルの作成

まずは普通のbatファイルを作ります。 その時、先頭にgotoを置きます。

@echo off
goto %1

サブルーチンの設定

コマンドプロンプトから実行したい内容をサブルーチンにします。 サブルーチンの最後はexit /bで呼び出し元に戻ります。

@echo off
goto %1

:sub
echo "これはサブルーチン"
exit /b

:not-called
echo "これは呼ばれない"
exit /b

コマンドプロンプトからの呼び出し

コマンドプロンプトからラベルを指定して実行します。

call makefile.bat sub
"これはサブルーチン"

気をつけること

ここで作ったbatファイルはそのままでは実行できなくなります。

参考

ひとつのbuild.gradle.ktsファイルでマルチプロジェクトを構成したい

概要

複数ファイルで構成するサンプルはよく見かけるけど、1つのファイルで構成するサンプルを見かけなかったので作ってみました。
これが正解かはわかりません。

環境

------------------------------------------------------------
Gradle 6.6.1
------------------------------------------------------------

Build time:   2020-08-25 16:29:12 UTC
Revision:     f2d1fb54a951d8b11d25748e4711bec8d128d7e3

Kotlin:       1.3.72
Groovy:       2.5.12
Ant:          Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM:          1.8.0_242 (AdoptOpenJDK 25.242-b08)
OS:           Mac OS X 10.15.6 x86_64

TL;DR

  • 全体の設定はallProjects
  • サブプロジェクト全体の設定はsubprojects
  • 各プロジェクト固有の設定はproject("MODULE_NAME")

どうすればいいか

とりあえず親プロジェクトを用意する

ゼロから用意するのは面倒なので、SpringInitializerで生成しちゃいます。
今回は親プロジェクトはナシで、サブプロジェクトになるものを2つ用意しました。
スタートはこんな感じです。ここから少しずつ分解していきます。

rootProject.name = "one-file-gradle-multi-project"

include("sub-project-1")
include("sub-project-2")
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.3.3.RELEASE"
    id("io.spring.dependency-management") version "1.0.10.RELEASE"
    kotlin("jvm") version "1.3.72"
    kotlin("plugin.spring") version "1.3.72"
}

group = "com.example.footaku.scratches"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3")
    runtimeOnly("org.postgresql:postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    }
}

全体に適用したいものを抜き出す

repositoriesは全体に関係するのでallProjectsに移動します。

allprojects {
    repositories {
        mavenCentral()
    }
}

サブプロジェクト全体に適用するものを分ける

すべてのサブプロジェクトで使うdependenciestaskssubprojectsへ移動します。
ついでにpluginsourceSetsの設定も足します。

subprojects {
    apply(plugin = "java")
    apply(plugin = "kotlin")
    apply(plugin = "kotlin-spring")
    apply(plugin = "org.springframework.boot")
    apply(plugin = "io.spring.dependency-management")

    group = "com.example.footaku.scratches"
    version = "0.0.1-SNAPSHOT"
    java.sourceCompatibility = JavaVersion.VERSION_1_8
    java.targetCompatibility = JavaVersion.VERSION_1_8

    sourceSets {
        main {
            java.srcDir("src/main/kotlin")
            resources.srcDir("src/main/resources")
        }

        test {
            java.srcDir("src/test/kotlin")
            resources.srcDir("src/test/resources")
        }
    }

  dependencies{...}

  tasks.withType<...> {...}
}

プロジェクト固有の設定を分ける

さらに、一部のプロジェクトだけに適用するdependenciesなどはprojectでプロジェクト名を指定します。
今回はDBを使う設定をsub-project-1にだけ適用してみます。
この場合sub-project-1はDataSourceの設定をapplication.yml(.properties)に書かないとSpringBootは起動できなくなりますが、sub-project-2ではその依存が入っていないので設定が無くても大丈夫です。

project(":sub-project-1") {
    dependencies {
        implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3")
        runtimeOnly("org.postgresql:postgresql")
    }
}

完成

出来上がりがこちらです。

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.3.3.RELEASE"
    id("io.spring.dependency-management") version "1.0.10.RELEASE"
    kotlin("jvm") version "1.3.72"
    kotlin("plugin.spring") version "1.3.72"
}

allprojects {
    repositories {
        mavenCentral()
    }
}

subprojects {
    apply(plugin = "java")
    apply(plugin = "kotlin")
    apply(plugin = "kotlin-spring")
    apply(plugin = "org.springframework.boot")
    apply(plugin = "io.spring.dependency-management")

    group = "com.example.footaku.scratches"
    version = "0.0.1-SNAPSHOT"
    java.sourceCompatibility = JavaVersion.VERSION_1_8
    java.targetCompatibility = JavaVersion.VERSION_1_8

    sourceSets {
        main {
            java.srcDir("src/main/kotlin")
            resources.srcDir("src/main/resources")
        }

        test {
            java.srcDir("src/test/kotlin")
            resources.srcDir("src/test/resources")
        }
    }

    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-web")
        implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

        testImplementation("org.springframework.boot:spring-boot-starter-test") {
            exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
        }
    }

    tasks.withType<Test> {
        useJUnitPlatform()
    }

    tasks.withType<KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict")
            jvmTarget = "1.8"
        }
    }
}

project(":sub-project-1") {
    dependencies {
        implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3")
        runtimeOnly("org.postgresql:postgresql")
    }
}

困っていること

バージョンの指定方法

Mavenではバージョンなどの値を<properties>で設定すればどこでも使えるけれど、 Gradleにはそういう機能がないっぽいのが困りどころだなーと感じてます。

ワークアラウンド?としてextを使う方法をよく見かけますが、こちらはpluginの指定では使えず、 一方でbuildSrcに定義すればどこでも使える様になる代わりに、定義する場所が分散してしまうのでうーん…
できれば「このファイルだけ見ればOK」という状態にしたいのですが、 自分なりに「これがベスト!」という方法が見つかってません。

あってるかがわからない

とりあえず動いてるけどこれが正しいのかがわかってません。

参考

FlywayのmigrateでcleanOnValidationErrorを試す

概要

flywayのコマンドmigrateにあるcleanOnValidationErrorというオプションを使うとどうなるかのメモです。 今回はPostgresで試してます。

環境

  • Java 8
  • Maven(mvnw経由) 3.5.4
    • flyway-maven-plugin 5.2.1
  • Postgres Driver 42.2.5

どうやる?

今回はmavenを使って実行したいのでpom.xmlを準備します。

...
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.2.5</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>5.2.1</version>
            </plugin>
        </plugins>
    </build>
...

migration用のSQLを準備します。 今回は2つのテーブルを作ってみます。

--V1__create_a.sql
CREATE TABLE AAA (
  id NUMERIC PRIMARY KEY
);
--V2__create_b.sql
CREATE TABLE BBB (
  id NUMERIC PRIMARY KEY
);

postgresはdockerで作ります。

docker run -d \
  --name postgres10 \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  postgres:10

どうなる?

1回目

mvnからflywayのmigrateコマンドを実行します。

./mvnw flyway:migrate \
  -Dflyway.url=jdbc:postgresql://127.0.0.1:5432/postgres \
  -Dflyway.user=postgres \
  -Dflyway.password=postgres \
  -Dflyway.driver=org.postgresql.Driver \
  -Dflyway.locations=classpath:db/migration
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example.panage:sample-flyway >------------------
[INFO] Building sample-flyway 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- flyway-maven-plugin:5.2.3:migrate (default-cli) @ sample-flyway ---
[INFO] Flyway Community Edition 5.2.3 by Boxfuse
[INFO] Database: jdbc:postgresql://127.0.0.1:5432/postgres (PostgreSQL 10.4)
[INFO] Successfully validated 2 migrations (execution time 00:00.026s)
[INFO] Current version of schema "public": << Empty Schema >>
[INFO] Migrating schema "public" to version 1 - create a
[INFO] Migrating schema "public" to version 2 - create b
[INFO] Successfully applied 2 migrations to schema "public" (execution time 00:00.147s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.394 s
[INFO] Finished at: 2018-12-01T12:27:09+09:00
[INFO] ------------------------------------------------------------------------

migrateが成功して、2つのSQLが実行されたことがわかります。

SQLを編集

ここでmigrateしたSQLの1つを編集してしまいます。

--V1__create_a.sql
CREATE TABLE AAA (
  id NUMERIC PRIMARY KEY,
  value VARCHAR(100) NOT NULL
);

2回目

今度はcleanOnValidationErrorのオプションを付けて実行します。

./mvnw flyway:migrate \
  -Dflyway.url=jdbc:postgresql://127.0.0.1:5432/postgres \
  -Dflyway.user=postgres \
  -Dflyway.password=postgres \
  -Dflyway.driver=org.postgresql.Driver \
  -Dflyway.locations=classpath:db/migration \
  -Dflyway.cleanOnValidationError=true ←ココ
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example.panage:sample-flyway >------------------
[INFO] Building sample-flyway 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- flyway-maven-plugin:5.2.3:migrate (default-cli) @ sample-flyway ---
[INFO] Flyway Community Edition 5.2.3 by Boxfuse
[INFO] Database: jdbc:postgresql://127.0.0.1:5432/postgres (PostgreSQL 10.4)
[INFO] Successfully cleaned schema "public" (execution time 00:00.044s)
[INFO] Creating Schema History table: "public"."flyway_schema_history"
[INFO] Current version of schema "public": << Empty Schema >>
[INFO] Migrating schema "public" to version 1 - create a
[INFO] Migrating schema "public" to version 2 - create b
[INFO] Successfully applied 2 migrations to schema "public" (execution time 00:00.175s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.858 s
[INFO] Finished at: 2018-12-01T12:29:06+09:00
[INFO] ------------------------------------------------------------------------

成功しました。 Successfully cleaned schema "public"と出て、schemaが一度cleanされていることがわかります。

注意

このオプションをつけると問答無用でschemaがキレイになってしまうので、すでにレコードが登録されているときは、そのレコードが消えていいのか確認してから実行しましょう。
また、公式にもWarning ! Do not enable in production !とあるように、限られた状況以外で本番の環境で使うのは避けるのが良さそうです。

参考

Command-line: migrate - Command-line: migrate - Flyway by Boxfuse • Database Migrations Made Easy.

Kotlinでtestcontainers-javaをつかう

概要

Testcontainersってなに?

プログラムからDockerコンテナを起動できるライブラリ 最近testcontainers-javaとJUnit5のjupiterへの親和性が高くなったらしいので試したメモです。

環境

  • Kotlin 1.3.10
  • JUnit-jupiter 5.3.1
  • Testcontainers-java 1.10.1

どうする

pomに依存を足す

依存にはTestcontainersとJUnit5と、ドキュメントにはでてきていないような気がするけど、 Testontainersが作っているJUnit5用のExtensionが必要です 今回はPostgreSQLのコンテナを試してみるのでPostgreSQL用の依存も追加します。

<!-- pom抜粋 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>${junit.jupiter.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>${testcontainers.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>${testcontainers.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>${testcontainers.version}</version>
    <scope>test</scope>
</dependency>

テストクラスを作る

クラスに@Testcontainersアノテーションをつけて、PostgreSQLContainerというクラスのインスタンスを作ってあげるだけでOK。 クラス変数として作成するとテストクラスのインスタンス生成時にDBが起動して、テストが全て終わったら停止します。 インスタンス変数にすると各テストメソッドごとに起動と停止が実行されます。

インスタンス生成時にデータベース名やUsername/Passwordも一緒に設定できます。

@Suppress("NonAsciiCharacters", "TestFunctionName")
@Testcontainers
class SampleTestContainer {

    companion object {
        @Container
        private val container = PostgreSQLCotainer()
                .withDatabaseName("postgres")
                .withUsername("postgres")
                .withPassword("")
    }

    @Test
    fun コンテナが起動している() {
        assertTrue(container.isRunning)
    }
}

と思ったら

SELF-TypeReferenceが使われてて、今のKotlinだとそのままインスタンス化はできない?らしいので、 継承したクラスを作ってお茶を濁すことに。。(この辺あまり詳しくないです

class PostgreSQLKotainer : PostgreSQLContainer<PostgreSQLKotainer>()

// ... 抜粋 ...
@Container
private val container = PostgreSQLKotainer()
// ...........

気をつけること

Dockerなのでどうしても起動に待ち時間が必要になります。 1回だけ実行するならそんなに気にならないけど、開発時に何回も実行するとなるとツライ。 テストを実行する前に起動して、テスト実行中はTRUNCATEDROPを使いつつ、すべてのテストが終わったら停止するような使い方が良さそうに感じました。

Bashでは'readonly'より'local -r'を使っていきたい

概要

Bashスクリプトを書いている時に、関数内に閉じた形で読み取り専用の変数を宣言できないか調べたメモです。

環境

$ bash --version
GNU bash, バージョン 4.4.23(1)-release (x86_64-apple-darwin17.5.0)

readonlyはグローバル

変数を読み取り専用として宣言できるreadonlyですが 宣言した変数自体はグローバル変数です。

#!/bin/bash

set -eu

function setHoge() {
    readonly hoge="This is hoge"
}

setHoge
printf "hoge = ${hoge}\n"

exit 0
$ ./readonly.bash
hoge = This is hoge

変数hogeは関数の中で宣言していますが、関数の外からも参照できています。

localはスコープ内に閉じられる

localを使えば変数を関数などのスコープ内に閉じられます。

#!/bin/bash

set -eu

function setHoge() {
    local hoge="This is hoge"
    printf "hoge = ${hoge}\n"
    hoge="This is hogehoge"
    printf "hoge = ${hoge}\n"
}

setHoge
printf "hoge = ${hoge}\n"

exit 0
$ ./local.bash
hoge = This is hoge
hoge = This is hogehoge
./local.bash: line 13: hoge: unbound variable

ただし、そのままでは書き換え可能です。

rオプションをつければ安心

localで変数を宣言する際に-rをつけると読み取り専用にできます。

#!/bin/bash

set -eu

function setHoge() {
    local -r hoge="This is hoge"
    printf "hoge = ${hoge}\n"
    hoge="This is hogehoge"
    printf "hoge = ${hoge}\n"
}

setHoge
printf "hoge = ${hoge}\n"

exit 0
$ ./local-r.bash
hoge = This is hoge
./local-r.bash: line 8: hoge: readonly variable

これで安心です。

参考

stackoverflow.com

DbSetupでPostgreSQLの範囲型とJSONデータ型を扱う

概要

テストでよく使うDbSetupで、PostgreSQLの範囲型とJSONデータ型をどう扱うのか試してみました。

環境

  • Kotlin 1.2.60
  • JUnit 5.2.0(jupiter)
  • Maven 3.5.4
  • DbSetup 2.1.0

JSONデータ型

テーブルを準備

JSONデータ型をもったテーブルを作ります。 JSONデータ型にはjsonjsonbの2種類ありますが、今回は検索に有利なjsonbを使います。

CREATE TABLE jsonb (
    id INT PRIMARY KEY,
    value JSONB NOT NULL
);

DbSetupから文字列としてINSERT

普通の文字列としてINSERTしてみます

val destination = DriverManagerDestination(url, username, password)

val insert = Operations.insertInto("json")
        .columns("id", "value")
        .values(1L, """{ "hoge": "fuga" }""")
        .build()
DbSetup(destination, insert).launch()

ダメでした。残念ながら型が違うということでエラーになります。

com.ninja_squad.dbsetup.DbSetupRuntimeException: org.postgresql.util.PSQLException: ERROR: column "value" is of type jsonb but expression is of type character varying
  ヒント: You will need to rewrite or cast the expression.

SQLをそのまま実行する

DbSetupにはJSONデータ型への変換が用意されていないようなので、素のSQLを実行するように変更します。

val insert = Operations.sql("""
    INSERT INTO jsonb ( id, value )
    VALUES ( ${1L}, '{ "hoge": "fuga" }')
""")
DbSetup(destination, insert).launch()

これはうまくいきました。

範囲型

テーブルを準備

範囲型にも何種類かありますが、daterangeを使います

CREATE TABLE range (
    id INT PRIMARY KEY,
    value daterange NOT NULL
);

DbSetupから文字列としてINSERT

こちらも普通の文字列としてINSERTしてみます。

val insert = Operations.insertInto("range")
        .columns("id", "value")
        .values(1L, "[2018-01-01, 2018-01-31]")
        .build()
DbSetup(destination, insert).launch()

やはりダメでした。

com.ninja_squad.dbsetup.DbSetupRuntimeException: org.postgresql.util.PSQLException: ERROR: column "value" is of type daterange but expression is of type character varying
  ヒント: You will need to rewrite or cast the expression.

SQLをそのまま実行する

JSONの時と同じように素のSQLでINSERTしてみます。

val insert = Operations.sql("""
    INSERT INTO range ( id, value )
    VALUES ( ${1L}, '[2018-01-01, 2018-01-31]')
""")
DbSetup(destination, insert).launch()

こちらもうまくいきました。




テストで便利なDbSetupですが、データベース固有の方言をサポートしていないところもあるようです。
そんな時は純粋にSQLを実行するのが良いようです。

PostgreSQLで日付の範囲型を使う

PostgreSQLの範囲型

PostgreSQLには範囲型という型が用意されています。

8.17. 範囲型

数値や日付の範囲型もありますが、今回は日時の範囲型であるtsrangeを使ってみます。

TABLEの作成

いつもと同じように、テーブルを作成するときの型にtsrangeを指定するだけです。

postgres=# CREATE TABLE hoge (
postgres(#     id INT PRIMARY KEY,
postgres(#     duration TSRANGE NOT NULL
postgres(# );
CREATE TABLE

INSERTはどうするか

こちらもいつもと同じようにINSERTを実行すれば大丈夫です。

postgres=# INSERT INTO hoge (id, duration)
postgres-# VALUES (1, '[2018-01-01, 2018-02-01)');
INSERT 0 1

SELECTはどうするか

ためしにそのままSELECTを試します。

postgres=# SELECT * FROM hoge;
 id |                   duration
----+-----------------------------------------------
  1 | ["2018-01-01 00:00:00","2018-02-01 00:00:00")

大丈夫そうです。

下限や上限だけ取りたい

組み込み関数を使うと上限や下限が取得できます

postgres=# SELECT LOWER(duration) FROM hoge;
        lower
---------------------
 2018-01-01 00:00:00
(1 row)

postgres=# SELECT UPPER(duration) FROM hoge;
        upper
---------------------
 2018-02-01 00:00:00
(1 row)

任意の日時が範囲内か知りたい

範囲型に対して@>を使って範囲内か確認できます

postgres=# SELECT * FROM hoge WHERE duration @> '2018-01-01 0:0:0' :: TIMESTAMP;
 id |                   duration
----+-----------------------------------------------
  1 | ["2018-01-01 00:00:00","2018-02-01 00:00:00")
(1 row)

postgres=# SELECT * FROM hoge WHERE duration @> '2018-02-01 0:0:0' :: TIMESTAMP;
 id | duration
----+----------
(0 rows)

検索条件が範囲内のときのみレコードを取得できました。




今回はすべての機能は試していませんが、他にもたくさん関数や演算子が用意されているので便利に使えそうです。

KotlinとJUnit5とTestInstanceアノテーション

概要

JavaでJUnit5を使っているときに、KotlinだとBeforeAllAfterAllはどうなるのだろうと試したメモです。

環境

  • Kotlin 1.2.60
  • JUnit 5.2.0(jupiter)

試したこと

インスタンスメソッドでいい?

インスタンスメソッドにBeforeAllAfterAllアノテーションをつけると怒られます。 (IDEA使ってるとメソッド名がグレーアウトになるので、事前に実行されなさそうなこともわかります)

package com.example.panage.junit5

import org.junit.jupiter.api.*


class TestInstancePerMethodSample {
    @BeforeAll
    fun execBeforeAllTest() {
        println("before all")
    }

    @BeforeEach
    fun execBeforeEachTest() {
        println("before each")
    }

    @Test
    fun hoge() {
        println("feature")
    }

    @AfterEach
    fun execAfterEachTest() {
        println("after each")
    }

    @AfterAll
    fun execAfterAllTest() {
        println("after all")
    }
}
org.junit.platform.commons.JUnitException: @BeforeAll method 'public final void com.example.panage.junit5.TestInstanceSample.execBeforeAllTest()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

    at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertStatic(LifecycleMethodUtils.java:59)
    at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findMethodsAndAssertStatic$0(LifecycleMethodUtils.java:83)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)

staticメソッドにするか、TestInstanceアノテーション(後述)を使うように言われます。

companion objectだけでいい?

companion objectの中に入れてみます。

    companion object {
        @BeforeAll
        fun execBeforeAllTest() {
            println("before all")
        }

        @AfterAll
        fun execAfterAllTest() {
            println("after all")
        }
    }
before each
feature
after each

Process finished with exit code 0

テストは実行できますが、BeforeAllAfterAllの2つは実行されません。

JvmStaticアノテーションをつけたら?

@JvmStaticを追加してみます。

    companion object {
        @BeforeAll
        @JvmStatic
        fun execBeforeAllTest() {
            println("before all")
        }

        @AfterAll
        @JvmStatic
        fun execAfterAllTest() {
            println("after all")
        }
    }
before all
before each
feature
after each
after all
Process finished with exit code 0

ちゃんとBeforeAllAfterAllのメソッドも実行されました。

TestInstanceアノテーションなら?

先程のエラーメッセージでTestInstanceアノテーションを使うように言われていたのでつけてみます。 クラスに@TestInstance(TestInstance.Lifecycle.PER_CLASS)を足して、BeforeAllAfterAllcompanion objectの外に出します。

package com.example.panage.junit5

import org.junit.jupiter.api.*

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestInstancePerClassSample {

    @BeforeAll
    fun execBeforeAllTest() {
        println("before all")
    }

    @AfterAll
    fun execAfterAllTest() {
        println("after all")
    }

    @BeforeEach
    fun execBeforeEachTest() {
        println("before each")
    }

    @Test
    fun testFeature() {
        println("feature")
    }

    @AfterEach
    fun execAfterEachTest() {
        println("after each")
    }
}
before all
before each
feature
after each
after all
Process finished with exit code 0

ちゃんと実行できました!

TestInstanceアノテーション

テストクラスのライフサイクルを変更するためのアノテーションです(そのまんま) PER_CLASSPER_METHODが用意されていて、何も設定していない状態だとPER_METHODになります。

PER_METHODは従来どおりの動作で、テストメソッド実行ごとにテストクラスのインスタンスが生成されます。
PER_CLASSでは各テストメソッドに、同じインスタンスが使われます。(@Nestedを併せて使うと少し話は変わってくるようです)

設定の変更はアノテーションの他に、MavenやSystemプロパティなどからデフォルトでどちらにするかを変えられます。 junit-platform.propertiesというファイルに書いてクラスパスルートに置くのが個人的には良いかな?と思っています。

TestInstanceとKotlinの関係はAPIドキュメントにも

Simplified declaration of @BeforeAll and @AfterAll methods in test classes implemented with the Kotlin programming language.

とあります。

デフォルトの設定を変えるのか、クラスごとに設定するのかはチームごとの方針に依るとは思いますが、 KotlinでBeforeAllAfterAllを使うときは、PER_CLASSにしておくと楽になりそうです。

参考

TestInstance (JUnit 5.1.0 API)

IntelliJ IDEA上でmvn-testをDebugするときはmaven-surefire-pluginが必要らしい

概要

mvn test中にBreakPointでとまらないなーと思って調べたメモです。

環境

  • IntelliJ IDEA 2018.2.2 (Ultimate Edition)

どうすればいいか

タイトルにも書いてありますが、maven-surefire-pluginを使います。 build pluginとして設定して、mvn実行時のオプションに-DforkCount=0を追加します。

pom.xml

<build>
...
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.0</version>
    </plugin>
  </plugins>
</build>

起動構成 f:id:panage:20180824112139p:plain

起動構成に入れるのを忘れがちな場合はConfigurationに追加してしまってもいいと思います。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.22.0</version>
  <configuration>
    <forkCount>0</forkCount>
  </configuration>
</plugin>

参考

stackoverflow.com

PostgreSQLでデフォルトの権限を設定する

コマンドはALTER DEFAULT PRIVILEGES

標準SQLにはないコマンドです。

USERに設定してみる

今回はDockerコンテナで環境を準備しました。

docker run -d \
  --name postgres10\
  -e POSTGRES_USER=postgres\
  -e POSTGRES_PASSWORD=postgres\
  postgres:10.4

コンテナ内からpsqlで接続します。

$ docker exec -ti postgres10 /bin/bash
$ su - postgres
$ psql -U postgres

新しいROLEを作成

postgres=# select session_user;
 session_user
--------------
 postgres

postgres=# CREATE ROLE testrole WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT LOGIN;CREATE ROLE

postgres=# CREATE TABLE test1 (id int);
CREATE TABLE

postgres=# \z
                             Access privileges
 Schema | Name  | Type  | Access privileges | Column privileges | Policies
--------+-------+-------+-------------------+-------------------+----------
 public | test1 | table |                   |                   |

SELECTできるか確認します。

$ psql -U testrole -d postgres
postgres=> select session_user;
 session_user
--------------
 testrole

postgres=> select * from test1;
ERROR:  permission denied for relation test1

権限が無いのでできません。

新しい権限を設定

今度はDEFAULT権限を変更したあとでTABLEを作ります。

postgres=# select session_user;
 session_user
--------------
 postgres

postgres=# ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES TO testrole;
ALTER DEFAULT PRIVILEGES

postgres=# CREATE TABLE test2 (id int);
CREATE TABLE

postgres=# \z
                                 Access privileges
 Schema | Name  | Type  |     Access privileges     | Column privileges | Policies
--------+-------+-------+---------------------------+-------------------+----------
 public | test1 | table |                           |                   |
 public | test2 | table | postgres=arwdDxt/postgres+|                   |
        |       |       | testrole=r/postgres       |                   |
(2 rows)
postgres=> select session_user;
 session_user
--------------
 testrole

 postgres=> select * from test2;
 id
----
(0 rows)

postgres=> select * from test1;
ERROR:  permission denied for relation test1

権限を設定したあとに作ったtest2テーブルは参照できました。
一方で、先に作ったtest1は参照できないままです。

設定した権限を削除

設定を削除してみます。

postgres=# select session_user;
 session_user
--------------
 postgres

postgres=# ALTER DEFAULT PRIVILEGES REVOKE SELECT ON TABLES FROM testrole;
ALTER DEFAULT PRIVILEGES

postgres=# CREATE TABLE test3 (id int);
CREATE TABLE

postgres=# \z
                                 Access privileges
 Schema | Name  | Type  |     Access privileges     | Column privileges | Policies
--------+-------+-------+---------------------------+-------------------+----------
 public | test1 | table |                           |                   |
 public | test2 | table | postgres=arwdDxt/postgres+|                   |
        |       |       | testrole=r/postgres       |                   |
 public | test3 | table |                           |                   |
(3 rows)
postgres=> select session_user;
 session_user
--------------
 testrole

postgres=> select * from test3;
ERROR:  permission denied for relation test3

postgres=> select * from test2;
 id
----
(0 rows)

設定を削除したあとのtest3テーブルは参照できませんが、
その前に作っていたtest2テーブルへの参照権限は残されたままです。
公式ドキュメントにあるとおり、すでに設定されている権限には影響しないので、DB構築初期に実施するのがよさそうです。

参考

ALTER DEFAULT PRIVILEGES