[Elm]キミだけのッ最強Fuzzerを手に入れろ！

世の中には property based testing と呼ばれるテスト手法があります。
テスト対象の関数が満たすべき **性質** を記述すると、その性質を調べるのに適切な入力データを自動生成して実際に関数にあたえ、その出力が想定している性質を満たしているかをチェックするテスト手法です。
Elm では [`elm-test` の `Fuzz` モジュール](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Fuzz) を使うのが一般的です。

Fuzz モジュールの基本的な使い方自体は [@jinjorさんがすでに記事にしてくれています](https://qiita.com/jinjor/items/429bf132119775a6f859) 。
この記事では、さらに Fuzzer の合成・変換について掘り下げることで、世界にひとつだけの目に入れても痛くないかわいい我が子のような Fuzzer を手に入れる方法をご紹介します。
ここで僕のかわいい愛娘のさくらちゃんをご覧ください。


![P_20171025_215601_vHDR_On.jpg](https://qiita-image-store.s3.amazonaws.com/0/16345/c92e9fe9-405e-27c6-237f-4ba8a275c23e.jpeg)


以降の内容は、`elm-test 4.20` (Elm 0.18) を想定しています。

## レベル0. プリミティブなやつ

オリジナルな `Fuzzer` って言っときながら、いきなり全然オリジナルじゃないやつです。

```elm
bool : Fuzzer Bool
-- `Int` の値を生成する.
-- `NaN` とか `Infinity` とか `-Infinity` とかは生成しません。
-- 他の Fuzzer もそうだけど、ただの一様分布ではなく、コーナーケースとかよく使われる値を多めに生成してくれます。
int : Fuzzer Int
float : Fuzzer Float
-- `0.0` から `1.0` の値を生成する.
-- もちろんこれもしっかり `0.0` とか `1.0` みたいな値を多めに生成します。
percentage : Fuzzer Float
-- 印字可能なASCII文字の文字列を生成します.
-- `""` も多めに生成します。
-- `""` だと困るようなケースはレベル3で `nonEmptyString` を作りましょう。
string : Fuzzer String
char : Fuzzer Char
```

## レベル1. 関数の引数でカスタマイズ

まずは `Fuzz` モジュールに用意されている関数に引数を与えることでオリジナル `Fuzzer` を手に入れましょう。
こういうやつです。

```elm
intRange : Int -> Int -> Fuzzer Int
floatRange : Float -> Float -> Fuzzer Float
constant : a -> Fuzzer a
```

以下のようにオリジナル `Fuzzer` を作れます。

```elm
myInt : Fuzzer Int
myInt = intRange -50 200

{-| さくらちゃんの胃袋のサイズを生成するFuzzer.
マイナスになることはないが、無限に草を食べるので上限が存在しない。
上限が無い場合には `Random.maxInt` を使います。
-}
stomachSize : Fuzzer Int
stomachSize = intRange 0 Random.maxInt

myFloat : Fuzzer Float
myFloat = floatRange -pi pi

{-| 常に同じ文字列を返すFuzzer.
そんなん Property based testing の意味がないじゃん！
ということで、これ単体で使うのではなく、後に紹介する `oneOf` とかと一緒につかいます。
-}
meanLess : Fuzzer String
meanLess = concatant "This always generates same string."
```
![P_20180422_163624_vHDR_Auto.jpg](https://qiita-image-store.s3.amazonaws.com/0/16345/63384ed6-6244-494e-dde4-739ac69807ac.jpeg)

## レベル2. かんたんな合成

レベル0 や レベル1の `Fuzzer` から `Maybe`, `Result`, `List` のような型の `Fuzzer` を作ってみましょう。

```elm
maybe : Fuzzer a -> Fuzzer (Maybe a)
result
    :  Fuzzer error
    -> Fuzzer value
    -> Fuzzer (Result error value)
list : Fuzzer a -> Fuzzer (List a)
array : Fuzzer a -> Fuzzer (Array a)
```

```elm
{-| 以下のいずれかが生成される.
* `myInt` で生成される `Int` に `Just` をつけたもの
* Nothing
-}
myMaybeInt : Fuzzer (Maybe Int)
myMaybeInt = maybe myInt

myResult : Fuzzer (Result Int Float)
myResult = result myInt myFloat

{-| ランダムな文字列をいくつか並べたリストを生成する.
空リストのこともある。
空リストを含めたくない場合はレベル3の `nonZeroList` を使います。
-}
myList : Fuzzer (List String)
myList = list string

myArray : Fuzzer (Array Bool)
myArray = array bool
```

## レベル3. 合成の方法を自分で指定する

```elm
map : (a -> b) -> Fuzzer a -> Fuzzer b
map2 : (a -> b -> c) -> Fuzzer a -> Fuzzer b -> Fuzzer c
...
andMap : Fuzzer a -> Fuzzer (a -> b) -> Fuzzer b
andThen : (a -> Fuzzer b) -> Fuzzer a -> Fuzzer b
```

```elm
-- 自分で作った型の Fuzzer を作る場合

data Point = Point Int Int
myPoint : Fuzzer Point
myPoint = map2 Point myInt int

-- 特殊な制約がある Fuzzer を作る場合

{-| `""` ではないことが保証されている文字列
1文字目と「空かもしれない文字列」をくっつければ必ず空ではない文字列になります。
-}
nonEmptyString : Fuzzer String
nonEmptyString =
    map2
        (\c str -> String.fromChar c ++ str)
        char string

{-| `[]` ではないListを作る関数
-}
nonZeroList : a -> Fuzzer (List a)
nonZeroList a =
    map2 (::) a (list a)

{-| `!` を含まない文字列
逆に限られた文字しか許さない文字列を作りたい場合は、
この方法では空文字列がたくさん生成されてしまうため、レベル4の方法をオススメします。
-}
mySpecialString : Fuzzer String
mySpecialString =
    map (String.filter (\c -> c /= '!')) string
```

## レベル4. 複雑な仕様に従う文字列を生成する

Parser を自分で書いてテストする場合などに重宝します。
[`elm-data-url`](https://github.com/arowM/elm-data-url)ではBNF記法で示されている仕様にしたがった文字列を生成し、
その結果を再度文字列にエンコードしたものと元の文字列が一致するか確認することで正常系のテストにしています。
BNFで書く必要がないくらい単純なものであればレベル3の `String.filter` を使ったもので十分ですが、`Regex` モジュールではなくあえて Parser を使うってことはたぶんこの手法を使うべき複雑なやつだと思います。

```elm
-- ListであたえたいずれかのFuzzerを使う
oneOf : List (Fuzzer a) -> Fuzzer a
-- `oneOf` とほぼ同じだけど、Listの要素の各Fuzzerの重要度を指定すると、
-- 重要度が高いFuzzerを優先的に使ってくれる。
frequency : List (Float, Fuzzer a) -> Fuzzer a
```

ためしに、[RFC6838 の `restricted-name`](https://tools.ietf.org/html/rfc6838#page-8)を生成する `Fuzzer` を作ってみます。

```elm
restrictedName : Fuzzer String
restrictedName =
    Fuzz.map2 (++)
        restrictedNameFirst
        restrictedNameTail


restrictedNameFirst : Fuzzer String
restrictedNameFirst =
    Fuzz.map String.fromChar <|
        Fuzz.oneOf
            [ upper
            , lower
            , digit
            ]


restrictedNameTail : Fuzzer String
restrictedNameTail =
    Fuzz.map (String.fromList << List.take 126) <|
        Fuzz.list <|
            Fuzz.oneOf
                [ upper
                , lower
                , digit
                , restrictedMark
                ]


restrictedMark : Fuzzer Char
restrictedMark =
Fuzz.oneOf <| List.map Fuzz.constant [ '!', '#', '$', '&', '-', '^', '_', '.', '+' ]
```

キミだけの `Fuzzer` を手に入れよう！

[![P_20180510_144042_vHDR_Auto.jpg](https://qiita-image-store.s3.amazonaws.com/0/16345/501450d3-63dd-d669-ca47-f8a9991f3842.jpeg)](https://twitter.com/hashtag/%E3%81%95%E3%81%8F%E3%82%89%E3%81%A1%E3%82%83%E3%82%93%E6%97%A5%E8%A8%98?src=hash)
[さくらちゃんにご飯をあげる](https://www.amazon.co.jp/gp/registry/wishlist/20ZV2VC57615B/ref=nav_wishlist_lists_1)