haxe-doctest を使ってみる

# はじめに

[haxe-doctest](https://github.com/vegardit/haxe-doctest)

というものを触ってみました。
元々 Python に doctest という機能が存在しており、それに触発されて作られたもののようです。

# 環境

windows 10
Haxe 3.3.0
haxe-doctest 1.0.1

# doctest

そもそも自分自身特に現状 Python がキッチリ書けるかと言われるとそうでもなく、
doctest なるものの存在自体初耳でした。

ということで↓をまず読んでみました

[Python の doctest の解説](http://docs.python.jp/3.5/library/doctest.html#module-doctest)

…要するにドキュメントコメントの中に簡易的なテストコード書けるし正当性のチェックもできるやで、ってことなんだと思います（雑
テストコードそのものは別ファイルに書いていくのが常套手段、だったと思うのですが、正直面倒な所があります。
これはメソッドのコメントに直接書けるので非常に敷居が下がって便利に見えますね。
テストそれ自体の目的を達するのもそうですが、**関数自体の使い方を載せる**用途としても、コード本体とかなり近いところに置けるのは利点といえると思います。
コメントと実装全然違うじゃねーかks、という状況が減らせそう

ちなみに、テストコードブロックの書き方を工夫すると、[dox](https://github.com/HaxeFoundation/dox)と組み合わせた時にいい感じの見た目（pre整形状態とか）で吐き出せます。

# 使ってみる

前述の github ページの Installation 辺りを見ればOK…と言いたい所なんですが、
同梱の example を確認しないとちょっとハマりそうな箇所があったので補足も兼ねて使用手順をメモります。

まず Installation の通り、`haxelib` を使って `haxe-doctest` をインストールします。

```
haxelib install haxe-doctest
```

## haxe-doctest をプロジェクトに登録

次に、`haxe-doctest` を作業中のプロジェクトに登録します。
OpenFL/Limeプロジェクトであれば `project.xml` らへんに記述。

```xml
<haxelib name="haxe-doctest" />
```

そうじゃないプロジェクトに関しては `*.hxml` に直接 `-lib` の記述を行ってください。

```hxml
-lib haxe-doctest
```

## テストランナーの作成

※この辺まだあんまり良くわかってないので間違った情報の可能性があります

プロジェクトへのライブラリ登録が済んだら、次にテスト実行のためのテストランナークラスを作成します。
READMEを確認した限りだと3種類くらいのテストフレームワーク（？）と組み合わせられるようですが、
今回試したのは `Haxe Unit` になるのでそちらの例を説明します。

とりあえずREADMEからのコピペでいいので以下のようなクラスを作成します

```haxe
@:build(hx.doctest.DocTestGenerator.generateDocTests())
class MyHaxeUnitTest extends haxe.unit.TestCase {

    public static function main() {
        var runner = new haxe.unit.TestRunner();
        runner.add(new MyHaxeUnitTest());
        runner.run();
    }

    function new() {
        super();
    }
}
```

ここで、作成したクラスの `generateDocTests()` に更に追記を行います。
`generateDocTests()` ですが、READMEからだとうまく追えないんですが実は3つほど引数が存在します。
筆者はここで少しハマったのですが、プロジェクトのフォルダ構成などによっては `generateDocTests()` 内の引数を適切に入力しないとさっぱりテストコードを認識してくれない事態なるので、気をつけてください。
引数ですが、ライブラリのコードコメントを読むと以下のような用途になります。

```
generateDocTests(srcFolder:String = "src", srcFilePathPattern:String = ".+\\.hx$", docTestIdentifier:String = "* >>>")
```

### srcFolder

テストコード解析を行うルートフォルダを指定します。
もしソースコード群のフォルダ名を `source` なんかにしている場合は、さっきのクラスに `source` と追記する必要があります。

### srcFilePathPattern

テストコード解析対象のファイルパスパターンを指定します。
ここは特に変える必要は感じませんでした。

### docTestIdentifier

ここで指定した文字列がコメントの行先頭でマッチすると、その行をテストコードとして扱います。
ちょっとコイツがクセモノで、 `dox` でコメントを書く際に

```
/**
    コメント～～～～
**/
```

こう書けるわけなんですが（というか、 `vscode` の `doc` スニペット使うと自ずとこういう形式になります）
`haxe-doctest` のコメント記述例を見る限りだとどうも

```
/**
 * コメント～～～～～～～～～～～～
 */
```

こんな書き方になっており、 `docTestIdentifier` のデフォルト値もそれに則した者になっているようです。
なので、もしここの引数を指定しないまま `doc` スニペットのノリで以下のようなコメントを書いても、テストコードとして認識されません。

```
/**
    >>> StringUtil.isValidName( null ) == false
**/
```

ということで、もし `vscode` を使っていてかつ `doc` スニペットも使っている、という場合は
こちらの引数も適切なものに変更した方が良いでしょう。

## 作成したクラスに、プロジェクトに合わせて追記

先程の3つの引数の用途を把握したら、今のプロジェクトに合わせてクラスに追記を行います。

例えば筆者のプロジェクトだと

* ソースコードのルートフォルダ名が `source`
* `vscode` を使っていて、かつ `doc` スニペット多用

の状態だったため、最終的に `generateDocTests()` は以下のような形になりました。

```
@:build( hx.doctest.DocTestGenerator.generateDocTests( "source", ".+\\.hx$", ">>>" ))
```

※
Haxeの構文に存在するのかがちょっと調べた限りだとわからなかったのですが、
1、3の引数は指定して2番目引数はデフォルト値で、ということをやる場合に
C#のように `hogehoge( int index = 0, bool hoge = false )` とかあったら `hogehoge( hoge:true )` みたいに書けないのかな、という疑問があります
少なくとも同じ書き方をした限りではエラー発生で動きませんでした

## コメント(テストコード)を書く

先程例に軽く書いちゃいましたが、まず例として以下のようなクラス、関数を用意してみます。

```
class StringUtil {
    public static function isValidName( str:String ):Bool {
        return str != null && str.length > 0;
    }
}
```

コメントを適当に書きます。

```
    /**
        str が名前として有効なものかどうかを返します
    **/
    public static function isValidName( str:String ):Bool {
        return str != null && str.length > 0;
    }
```

テストコードを追加します
テストは複数書くこともできます。

```
    /**
        str が名前として有効なものかどうかを返します

        >>> StringUtil.isValidName( null ) == false
        >>> StringUtil.isValidName( "h" ) == true
        >>> StringUtil.isValidName( "hoge" ) == true
        >>> StringUtil.isValidName( "" ) == false
    **/
    public static function isValidName( str:String ):Bool {
        return str != null && str.length > 0;
    }
```

もし `dox` を使っているのなら、以下のようにしとくと整形状態で吐き出されるのでちょっと見やすくなるかもしれません

```
    /**
        str が名前として有効なものかどうかを返します

        <pre><code>
        >>> StringUtil.isValidName( null ) == false
        >>> StringUtil.isValidName( "h" ) == true
        >>> StringUtil.isValidName( "hoge" ) == true
        >>> StringUtil.isValidName( "" ) == false
        </code></pre>
    **/
    public static function isValidName( str:String ):Bool {
        return str != null && str.length > 0;
    }
```

## 実行

実行準備が整ったので、とりあえず実行してみましょう。
メイン関数など必ず通る辺りのところに

```
MyHaxeUnitTest.main();
```

と記述し、コンパイル・実行します。
すると解析のログがガーっと流れた後、

```
Class: MyHaxeUnitTest 
StringUtil.hx:9 [OK] StringUtil.isValidName( null ) == false.
StringUtil.hx:10 [OK] StringUtil.isValidName( "h" ) == true.
StringUtil.hx:11 [OK] StringUtil.isValidName( "hoge" ) == true.
StringUtil.hx:12 [OK] StringUtil.isValidName( "" ) == false.
OK 4 tests, 0 failed, 4 success
```

このようなログが出て来るはずです。
この場合、4テストあって失敗0の成功4、ということでテスト通過、ということになります。
もし、この時例えばテストコードの後ろに`;`をつけてしまったなど**テスト側の構文記述をミスった場合**、

```
Class: MyHaxeUnitTest F
StringUtil.hx:12 [OK] StringUtil.isValidName( "" ) == false.
* MyHaxeUnitTest::testStringUtil_1()
ERR: source/lib/util/StringUtil.hx:9(.) - StringUtil.isValidName( null ) == false; --> Failed to parse assertion: Unexpected ;
Called from hx/doctest/internal/adapters/HaxeUnitDocTestAdapter.hx line 46
Called from E:\HaxeToolkit\haxe\std/neko/_std/Reflect.hx line 58
Called from E:\HaxeToolkit\haxe\std/haxe/unit/TestRunner.hx line 117
FAILED 2 tests, 1 failed, 1 success
```

こんな感じで記述エラーの報告が行われます。
これとは別に、普通にテストが通らなかった場合は

```
Class: MyHaxeUnitTest 
StringUtil.hx:9 [OK] StringUtil.isValidName( null ) == false.F
StringUtil.hx:11 [OK] StringUtil.isValidName( "hoge" ) == true.
StringUtil.hx:12 [OK] StringUtil.isValidName( "" ) == false.
* MyHaxeUnitTest::testStringUtil_2()
ERR: source/lib/util/StringUtil.hx:10(.) - StringUtil.isValidName( "h" ) == false --> Left side 'true' does not equal 'false'.
Called from hx/doctest/internal/adapters/HaxeUnitDocTestAdapter.hx line 46
Called from E:\HaxeToolkit\haxe\std/neko/_std/Reflect.hx line 58
Called from E:\HaxeToolkit\haxe\std/haxe/unit/TestRunner.hx line 117
FAILED 4 tests, 1 failed, 3 success
```

こんな感じで報告されます。
ここまで出来たらとりあえず一通り使えた、と言っていいと思います~~そう思いたい~~

# 雑感

用途としては簡易的なテストに限る…？のかもしれませんが、
それにしてもだいぶテスト書く敷居が下がった気がするので、これからどんどん使ってみたくなるライブラリでした。

…といっても、今のところHaxeは一人でしか書いてないんですが（哀
C#にもこういうの欲しいなあとか思ったりも。もしかしてあったりする？