D言語の単体テストが更に便利になる silly の紹介

# はじめに

D言語の単体テスト機能をさらに便利にするために作られた `silly` というパッケージの紹介です。

これは「単体テストの実行」をリッチにするライブラリで、最近はDUBレジストリの人気パッケージでもTOP3の常連になってきています。

- https://code.dlang.org/packages/silly


導入は非常に簡単ながら、ざっくり以下のことができるようになります。
ぜひ使ってみて下さい。

- `unittest` ブロック毎に結果の成否をカウントする
- `unittest` ブロック毎の処理時間を表示する
- テストに名前を付ける（Naming Tests）
- テストを複数スレッドで分散して実行する
- テストを正規表現でフィルタする

# 前置き（準備）

まず適当なパスに普通にプロジェクトを作っておきます。

```console:
dub init sandbox-silly -n
cd sandbox-silly
```

続けて単体テストを書きます。2つ書いておきます。

```d:app.d
import std.stdio;

void main()
{
    writeln("Edit source/app.d to start your project.");
}

unittest
{
    assert(1 + 2 == 3);
}
unittest
{
    assert(2 + 3 == 5);
}
```

1回実行してみます

```console:テスト実行
dub test
```
```console:結果
Running .\sandbox-silly.exe 
1 unittests passed
```

`app` というモジュールでテストが全部成功したので、 `1 unittests` がパスしたよ、となってます。

基本的に全部成功する前提であればこれで十分ですが、TDDやBDDだと開発中は失敗続きになるので少しわかりづらい部分もあります。

`silly` を導入すると、これがもっといい感じの表示になります。

# 導入

ではここに `silly` を導入していきます。
やることは2つです。

1. `dub` の設定ファイルに単体テスト用の `unittest` という `configuration` を追加し、依存関係を追加する
2. `main` 関数のあるプログラムであれば条件コンパイル等で除外しておく

## 依存関係の追加

sillyの最新バージョンが分からないので、とりあえず `dub add` コマンドで最新版を追加して書き換えます。

```console:
dub add silly
```

```json:dub.json
{
  "name": "sandbox-silly",
  "dependencies": {
    "silly": "~>1.0.2"
  }
}
```

2020年6月14日現在、最新バージョンは `1.0.2` でした。

ここから `dub test` で使われる `unittest` という名前の `configuration` を追加し、`dependencies` も移動します。

また、元の実行ファイルを作る設定も残しておくため `executable` というのも書いておきます。こちらは `default` とかでも良いです。

```json:dub.json
{
  "name": "sandbox-silly",
  "configurations": [
    {
      "name": "executable"
    },
    {
      "name": "unittest",
      "dependencies": {
        "silly": "~>1.0.2"
      }
    }
  ]
}
```


## エントリポイントの除外

`silly` は独自のエントリポイントを持っているライブラリなので、アプリ側で `main` 関数を書かないようにする必要があります。

ライブラリであれば何もしなくて大丈夫ですが、今回は `main` 関数があるので `version (unittest)` を使い条件コンパイルで消しておきます。

```d:
version (unittest) {}
else
{
	void main()
	{
		writeln("Edit source/app.d to start your project.");
	}
}
```

# 実行

さて、導入はこれだけです。もう1回実行してみましょう。
テスト実行のコマンドは変わりません。

```console:テスト実行
dub test
```

__テスト結果__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11612/1d2ad911-1a86-e793-7da5-e6742aedf170.png)
結果の表示が変わりました。色付きなので画像にしています。

`unittest` のブロック単位でカウントされ、どのテストが成功したのか失敗したのか、が色付きで表示されます。
チェックマークの直後がモジュール名、その次がテスト名（単体テストの関数名、Lの後が行数）です。

また実行時間が表示されるのもいい感じですね。

## 失敗時

`assert(2 + 3 == 6)` として失敗させてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11612/db036738-3aa5-8564-5e97-3a7726099762.png)
failedのほうが1つ増えました。

ちなみにVSCodeならスタックトレースのファイル名のあたりにマウスを載せるとCtrl+Clickで失敗した場所に移動できます。

と、以上が基本的な使い方です。

以下、さらに便利な部分について解説します。

# 機能

## 名前付きテスト

単体テスト1つ1つに名前を付けられます。
分かりやすい名前をつけておくと結果もわかりやすくなります。

```d:
@("1+2==3")
unittest
{
    assert(1 + 2 == 3);
}
```

`@("...")` の部分は言語的には `UDA` (User Defined Attribute)という機能で、関数に文字列を属性として付与しています。
`silly` はこの文字列の `UDA` をテストの名前として認識します。

文字列の `UDA` は言語の標準機能なので、 `silly` を `import` したり特別覚えることもないのは良いところかなと思います。

## マルチスレッド化

`silly` のテスト実行は、既定で複数スレッドで実行されます。

単体テストは独立順不同で実行できるという想定なので、マルチスレッド化すれば単純に早く終わるだろう、という感じです。
しかし、グローバル変数を触るようなテスト、外部ファイルを書いたりするテストはマルチスレッドだと具合が悪いので無効化したいときもあります。

無効化するときは、引数として、 `-t 1` や `--threads=1` といった感じで指定します。
`0` はコア数から自動で決定、あとは好きな数を設定すればOKです。

```console:スレッド数を指定する
dub test -- -t 1
```

なお、引数の指定で `dub test` の後に ` -- ` と挟んでいる点に注意が必要です。
`dub` そのものに対する引数と `dub test` の実行に関わる引数を分ける意味があります。

## テストのフィルタリング

`silly` の目玉機能です。

モジュール名やテスト名に対して正規表現でフィルタリングすることができます。

含めたい場合は、 `-i` や `--include` で指定、
除外する場合は、 `-e` や `--exclude` で指定します。

```console:appモジュールだけテスト
dub test -- -i app
```

```console:appの1+2は除外する
dub test -- -i app -e 1\+2
```

※ `dub test` の後に ` -- ` と挟んでいるのに注意です

今書いてるモジュールだけテストしたいときなど、割と気軽にテストを絞れるので非常に便利です。

また名前の付け方によって優先度を表現したりできるので、時間のかかるテストを除外するなどしたいときはこのあたりで調整すると良さそうですね。

複数スレッドで実行してほしくないグループ毎に名前を付けておいて、それを `-i` と `-t 1` で調整すれば高度な制御もできるようになります。

## 詳細表示

`-v` や `--verbose` というフラグを付けることで詳細表示モードになります。

- テスト毎に実行時間が表示される
- `silly` のテストランナーも含めたスタックトレースが表示される

遅いテストを特定できるので、除外したり高速化することで日頃の開発が更に効率的になると思います。

# テストランナーの差し替え

D言語は、単体テストのテストランナー切り替え機能をランタイム機能として提供しています。

`Runtime.extendedModuleUnitTester = &customTester;` という感じで差し替えるだけでよく、自力で書くのも簡単です。

以下にサンプル含めたドキュメントがあるので、興味がある方は参照してみてください。

- `Runtime.extendedModuleUnitTester`
    - https://dlang.org/phobos/core_runtime.html#.Runtime.extendedModuleUnitTester