D言語のテストにdub testを使う冴えたやり方

D言語にはdubというパッケージマネージャ兼ビルドツール兼テストツールがあります。
今回はdubのテストツールとしての側面について調べがついたことを述べます。

# 概要
1. dub testで思い通りにいかない場合は、以下をチェック
2. 設定にconfigurationを記載すること
3. そのconfigurationのtargetTypeを指定すること
4. dub test時にデフォルトで選択される"unittest" configuratinも明示的に記載すること
5. targetTypeがexecutableなら、mainSourceFileも指定すること


# dub testつかおう

`dub test`とコマンドを打つと、dubプロジェクトをテストすることができます。
一言で書くとそうなのですが、実際に使おうと思うといくつか障壁があります。

1. メイン関数実行されない問題
2. メイン関数ダブる問題
3. メイン関数がないって言われる問題
4. --main-file指定すると挙動違う問題
5. ライブラリとアプリケーションで挙動違う問題
6. mainSorceFile指定すると挙動違う問題
7. sourcePaths指定すると挙動違う問題
8. configurationsがあると挙動違う問題

`dub test`は、メイン関数を含む***単体テスト用メインソースファイル***を自動生成して一緒にコンパイルしてくれるという機能を持っています。
クッソおせっかいな機能で、これが随所で邪魔をしてきます。
これがあるので私は最近まで`dub test`を使わずにいました。

でもせっかくある機能なのだから有効活用したい…ので、この意味不明な挙動を完全に掌握し、先述の各種問題をコントロール下に置きたい！と思ったので調べました。

# ルールを覚えてdub testがやってくれることを理解しよう

dub testのおせっかいにはルールがあります。ルールを覚えると何をすればいいのかがおのずと見えてくるはず…！

### ルール1: ***メインソースファイル***
***メインソースファイル***というのは、メイン関数**だけ**を含むのソースファイルのことです。
dub testは***単体テスト用メインソースファイル***を自動的に生成し、***メインソースファイル***と置き換えます。

通常ライブラリでは***メインソースファイル***を含めませんが、単体テストではメイン関数を含む実行ファイルにしなければならないため、dub testでは***単体テスト用メインソースファイル***を自動的に生成して、一緒にコンパイルしてくれます。

一方、通常のアプリケーションでは、単体テストでメイン関数が実行されてしまうと困る場合が多々あります。サーバーアプリやGUIアプリなんかを考えるとわかりやすいですね。`dub test`で一発で結果が出ずに、サーバーアプリではクライアントとのやり取り、GUIではユーザーの操作なんかが必要となり、再現性のあるテストができなくなるからです。(これらは`dub test`のスコープ外です)
このため、dub testでは実際のメイン関数が実行されないように***メインソースファイル***と、空のメイン関数を含む***単体テスト用メインソースファイル***を置き換えます。

でもまあ、ライブラリであっても設定ファイル(dub.json/dub.sdl)で`mainSourceFile`を指定していれば、***単体テスト用メインソースファイル***と置き換わります。

この`mainSourceFile`置き換えという仕様が、障壁1.「メイン関数実行されない問題」の原因です。また、逆に`mainSourceFile`が指定されていないと、単純にすべてのソースファイルに加えて、***単体テスト用メインソースファイル***が追加されてビルドされ、自分で定義したメイン関数とダブってしまい、障壁2.「メイン関数ダブる問題」を引き起こしてしまいます(条件コンパイルで回避する必要があります)。

さらに注意事項として、`source/app.d`などのファイルがあっても、それが必ず`mainSourceFile`として推測されるわけではありません。`source/app.d`があった場合にそれがアプリケーションのメインソースファイルと推測されるのは、**configurationがない場合に限る**のです。(つまり、一つでもconfigurationがある場合、ちゃんと`mainSourceFile`を指定しないとメイン関数が重複してしまいます。)


### ルール2: `--main-file`オプション
ルール1で、dub testでは***単体テスト用メインソースファイル***が使われると言いました。この--main-fileオプションは、dubが生成した***単体テスト用メインソースファイル***の代わりにメイン関数を定義したファイルを追加で指定ができるというオプションです。ただし、ライブラリの場合限定です。(なぜライブラリだけで使えるか、の理由は不明です)

メイン関数を含むファイルを自作したい場合はこのオプションを指定してやることで、勝手に作ったファイルではなく、自分自身が作ったファイルでテストを行うことができます。

なお、このファイルには必ずメイン関数が必要です。これを忘れると障壁3.「メイン関数がないって言われる問題」の原因になります。
なんか、これを指定するとルール3が適用されなかったりとかなんやかんやあるみたいで、障壁4.「--main-file指定すると挙動違う問題」につながっていきます。


### ルール3: unittest configuration
dub testでは、`--config`で構成を指定しない場合で、dub設定の中に`unittest`という名前の構成(configuration)がある場合、それを利用するというおせっかいもあります。使用される構成は、以下のようにして決定されます。

1. `--config`で指定される構成
2. `unittest`という名称の構成
3. 最初に出てくるライブラリの構成
4. 最初に出てくるアプリケーションの構成

要するに dub build と違って、最も最初に出てくる構成が選ばれるわけではない、というのがポイントです。


# どうすればいいのか？
上に簡単に書きましたが、実際にはもっと複雑です。例えばconfigurationがない場合や、source/app.dなどの有無、sourcePathsの設定など。主に、デフォルト構成の自動生成周りでいろいろと予想外？な動きをするようです。(この辺が障壁5～8の原因)
この辺りは複雑すぎてまじめに説明するとdubのプログラム内部の詳細を書くような感じになってしまうので(dubは残念ながらそういう感じの残念コードです)、以下に失敗しないためのテンプレを記載しておきます。

### アプリケーションの場合その1
以下に従いプロジェクトを作成します。

1. "application" configurationを作成する(名称は任意だが、デフォルトで選択されるように、一番先頭に定義する。ちなみにアプリケーションタイプの場合、configurationの定義を省略するとデフォルトは"application"になる。)
2. "unittest" configurationを必ず作成する
3. mainSourceFileを必ず指定する
    1. mainSourceFileには、メイン関数**だけ**を記載すること。(単体テストや、ほかの関数は一切含めない。)
4. dub test実行時には、理由がない限り`--config`は使用しない。
    1. どうしても指定したい場合は、指定するconfigにもmainSourceFileを指定する。
5. dub test実行時には、理由がない限り`--main-file`は使用しない。

```json:dub.json
{
    "authors": ["作者名"],
    "description": "プロジェクトの説明",
    "license": "public domain",
    "name": "プロジェクト名",
    "configurations": [
        {
            "name": "application",
            "targetType": "executable",
            "mainSourceFile": "source/app.d"
        },
        {
            "name": "unittest",
            "mainSourceFile": "source/app.d"
        }
    ]
}
```

```d:source/app.d
import myapp.main;
// 自分で作ったメイン関数(myMain)呼ぶだけみたいなのが望ましい。
int main(string[] args) { return myMain(args); }
```

### アプリケーションの場合その2
以下に従いプロジェクトを作成します。メイン関数書くところにもっといろいろ書きたいとか、dub testのためだけに関数コールの階層深くなるの嫌なんだけどっていう人向け。※でも結局`version (unittest)`みたいなのを書かないと行けなくてちょっと汚くなる

1. "application" configurationを作成する(名称は任意だが、デフォルトで選択されるように、一番先頭に定義する。ちなみにアプリケーションタイプの場合、configurationの定義を省略するとデフォルトは"application"になる。)
2. "unittest" configurationを必ず作成する
3. mainSourceFileは指定しない("unittest" configurationの中身だけでなく、外側にも指定しないこと)
4. dub test実行時には、理由がない限り`--main-file`は使用しない。
5. 念のため、source/app.d や src/main.d などの名称は避ける(今のところmainSourceFileだと推定はされないはずだが、将来的にはわからない)
6. メイン関数は、`version (unittest)`などで、dub test時には定義されないようにすること。


```json:dub.json
{
    "authors": ["作者名"],
    "description": "プロジェクトの説明",
    "license": "public domain",
    "name": "プロジェクト名",
    "configurations": [
        {
            "name": "application",
            "targetType": "executable"
        },
        {
            "name": "unittest",
            "versions": ["DubTest"]
        }
    ]
}
```

```d:source/myapp.d
import hoge.foo;
pragma(lib, "fuga");
// 条件コンパイルでdub testのときはメイン関数が定義されないようにすること
// ここでは unittest 全部で定義されないようにしていますが、
// 上記設定ファイルのように"DubTest"というバージョンが定義されているのであれば、
// 代わりにDubTestとしても大丈夫です。
// これは `dub build -b=unittestでビルドだけ行って、その後実行したい場合に便利です。
version (unittest) { } else
int main(string[] args) { return foo(args); }
```

### ライブラリの場合
以下に従いプロジェクトを作成します。ただし、configurationによってライブラリにもアプリケーションにもなりうるプロジェクトの場合は、アプリケーションの場合を優先して実施してください。

1. "library" configurationを作成する(名称は任意だが、デフォルトで選択されるように、一番先頭に定義する。ちなみにライブラリタイプの場合、configurationの定義を省略するとデフォルトは"library"になる。)
2. "unittest" configurationを必ず作成する
3. mainSourceFileは指定しない("unittest" configurationの中身だけでなく、外側にも指定しないこと)
4. dub test実行時には、理由がない限り`--config`は使用しない。
5. dub test実行時には、理由がない限り`--main-file`は使用しない。

```json:dub.json
{
    "authors": ["作者名"],
    "description": "プロジェクトの説明",
    "license": "public domain",
    "name": "プロジェクト名",
    "configurations": [
        {
            "name": "library",
            "targetType": "library"
        },
        {
            "name": "unittest",
            "targetType": "library"
        }
    ]
}
```

#まとめ

いろいろ書きましたが、要するに設定ファイルでconfigurationにunittest構成を作り、targetTypeをちゃんと書いて、それがexecutableな場合はmainSourceFileを指定する(またはversionでdub test時にメイン関数が定義されないようにする)ことで、dub testで比較的素直に動かすことができる、ということでした。

今回この記事を書くにあたり、ソースコードの中身まで読んで調べていますが、勘違いやもっといい方法を見落としているかもしれません。もしご指摘ありましたら、コメントしていただけると助かります。