ユニットテストでファイルを扱うサンプルコード

# 概要

Kotlinを利用したプロジェクトのユニットテストで、ファイルを操作するクラスのテストや、テストデータをファイルからロードする場合など、ファイルを扱う処理に関するサンプルコードです。

**環境**

* Windows 10 Professional
* Java 1.8.0_162
* Kotlin 1.2.21
* JUnit 4.12
* IntelliJ IDEA 2017.3

**参考**

* [Calling Kotlin from Java] (https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html)

# TemporaryFolderで一時ディレクトリ／ファイルを扱う

[TemporaryFolder] (http://junit.org/junit4/javadoc/4.12/org/junit/rules/TemporaryFolder.html)は、JUnitが提供するルールの1つでテストメソッドの実行前に一時ディレクトリを作成、実行後に削除を行います。

JavaでTemporaryFolderを使うには、publicなフィールドにRuleアノテーションを付けます。

```java:Java
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
```

もしくはpublicなgetterにRuleアノテーションを付けます。

```java
private TemporaryFolder tempFolder = new TemporaryFolder();

@Rule
public TemporaryFolder getTempFolder() {
    return tempFolder;
}
```

Kotlinでは、tempFolderプロパティをJavaのpublicなフィールドとして扱うためにJvmFieldアノテーションを付けます。

```kotlin:Kotlin
@Rule
@JvmField
val tempFolder: TemporaryFolder = TemporaryFolder()
```

JvmFieldアノテーションを付けないと、JavaのprivateなtempFolderフィールドにRuleアノテーションが付いてしまい、ユニットテスト実行時に下記のエラーになります。

```
org.junit.internal.runners.rules.ValidationError: The @Rule 'tempFolder' must be public.
```

JvmFieldアノテーションを使わずに、次のように書き直すこともできます。
この書き方にするとtempFolderフィールドはprivateのままですが、RuleアノテーションがtempFolderフィールドのpublicなgetterメソッドに付き、実行時エラーになりません。

```kotlin
// apply @Rule annotation to property getter
@get:Rule
val tempFolder: TemporaryFolder = TemporaryFolder()
```

TemporaryFolderクラスに下記のような共通処理を拡張関数として実装しておくと便利です。

```kotlin
// リソースファイルを一時ディレクトリへコピーする
fun TemporaryFolder.copyFile(srcFileName: String, dstFileName: String) {
    val srcPath: Path = Paths.get(this.javaClass.classLoader.getResource(srcFileName).toURI())
    val dstPath: Path = Paths.get(root.absolutePath, dstFileName)
    Files.copy(srcPath, dstPath)
}

// 一時ディレクトリにあるファイルのパスを返す
fun TemporaryFolder.getPath(fileName: String): Path =
    Paths.get(root.absolutePath, fileName)
```

## 用例1) テストファイルを一時ディレクトリへコピーしてから使う

```kotlin
// (1)
tempFolder.copyFile("test_template.txt", "test.txt")

// (2)
val testFile = tempFolder.getPath("test.txt")

// (3)
// testFileを使った何かしらのテスト
```

* (1) : `src/test/resources/test_template.txt`を一時ディレクトリへtest.txtという名前でコピーします。
* (2) : 一時ディレクトリにあるtest.txtを取得します。
* (3) : test.txtを使った何かしらのテストを実行します。

一時ディレクトリの一時ファイルを使ったテストなので後始末は不要です。テストを実行するたびに新しく作り直されます。

## 用例2) テストデータを一時ファイルに書き出して使う

```kotlin
// (1)
val testFile = tempFolder.newFile("test.txt").toPath()

// (2)
val textData = listOf("first line", "2nd line", "3rd line", LocalDate.of(2018, 1, 26).toString())
Files.write(testFile, textData)

// (3)
// testFileを使った何かしらのテスト
```

* (1) : test.txtという一時ファイルを作成します。
* (2) : 一時ファイルにテストデータを書き込みます。
* (3) : 一時ファイルを使った何かしらのテストを実行します。

## ClassRuleアノテーションを使う

Ruleアノテーションではテストメソッド単位にルールが適用されますが、ClassRuleアノテーションを使うとテストクラス単位になります。
なお、ClassRuleはstaticフィールドに付けます。

```java:Java
@Rule
public static TemporaryFolder tempFolder = new TemporaryFolder();
```

Kotlinではコンパニオンオブジェクトに定義します。

```kotlin:Kotlin
companion object {
    @ClassRule
    @JvmField
    val tempFolder: TemporaryFolder = TemporaryFolder()
}
```


# Classpath上のファイルを扱う

ユニットテストで使用するテストファイルを、たとえば`src/test/resources/data/template`下に配置している場合、次のようなユーティリティオブジェクトを使って扱うことができます。

```kotlin
object TestFileUtils {
    fun getResourceFile(fileName: String, dir: String = "data/template"): Path =
        Paths.get(this.javaClass.classLoader.getResource("$dir/$fileName").toURI())
}
```

## 用例1) 任意のファイルにアクセスする

`src/test/resources/data/template/test.txt`ファイルを読み取りたい場合

```kotlin
val testFile: Path = TestFileUtils.getResourceFile("test.txt")
```

別のディレクトリにあるファイルを指定したい場合

```kotlin
val testFile: Path = TestFileUtils.getResourceFile("test.txt", "data/other")
```

## 用例2) プロパティファイルを扱う

kotlinのuse関数を使うことでリソースを自動的に閉じてくれます。
この例では、`src/test/resources/data/config/test.properties`をロードしています。

```kotlin
val testFile: Path = TestFileUtils.getResourceFile("test.properties", "data/config")

val props = Properties().apply {
    Files.newInputStream(testFile).use { reader -> load(reader) }
}

// debug dump
props.toList().joinToString(", ").also { println(it) }
```

## 用例3) JSONファイルを扱う

この例ではjackson-module-kotlinを使用しています。（普通のJsonファイルを扱う処理なので特筆することはありません。）

```kotlin
val testFile: Path = TestFileUtils.getResourceFile("template.json")

val jsonData: TestDataJson = jacksonObjectMapper().readValue(testFile.toFile())

// debug dump
jsonData.also { println(it) }
```