[Elm] Fuzzer でテストデータを量産しよう

Elm2 アドベントカレンダー 2017 の４日目です。

elm-test に搭載されている機能 Fuzzer を紹介します。

## Fuzzer とは

Fuzzer とは [elm-test](http://package.elm-lang.org/packages/elm-community/elm-test/latest) に標準で付いてくる**テストデータの自動生成機能**です。 [Fuzz](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Fuzz) モジュールによってこの機能が提供されています。

公式の例を引用します。これは「任意の文字列に対して２回 `String.reverse` を適用すると元の文字列に戻る」というテストです。

```elm 
import Expect
import Test exposing (Test)
import Fuzz exposing (..)

suite : Test
suite =
    describe "The String module"
        [ describe "String.reverse"
            -- fuzz runs the test 100 times with randomly-generated inputs!
            [ fuzz string "restores the original string if you run it again" <|
                \randomlyGeneratedString ->
                    randomlyGeneratedString
                        |> String.reverse
                        |> String.reverse
                        |> Expect.equal randomlyGeneratedString
            ]
        ]
```

コメントによると「ランダムなインプットを使って 100 回テストを実行した」と言っています。どんなインプットが生成されているのか気になるので、ここで `Debug.log` を挟んで確認してみます。

```diff
                    randomlyGeneratedString
+                       |> Debug.log "str"
                        |> String.reverse
                        |> String.reverse
                        |> Expect.equal randomlyGeneratedString
```

すると ↓ こういうものが出てきます。

```
str: "o^i:WCk%LQ1A7Nrmy/\\`C'/7ehRQSRHK:zBi`E|[iM<xpIA"
str: "qe}[8F?a#"
str: "W)Ov+c<44"
str: "4b1\\S-"
str: "oTd2"
str: "XXb1q'}Zv2}&gzItPe|cgRagI\\7Xn4j#<55SO:W*L]s"
str: "[;>rd;oz T1L|6?X<N0)w1wRc#DTV4ub^i`Ld!Eup2GV(nF>ai\\}Toy6J6e%'zjf Kl+eV\\v_EH<_4i\\L*m@`zTD3+wAJickX'V\"OKn\"|Iye?|Xs6}4uhv,RPr1Z]c\\5\\mD;]^}TB-yTtQleU!Rl[zMo5p;*)EH_1-OdWjRgo@.(H\\rnN'1$+`~9{&'_VL/PP#nZBYB+vL@*k:fpcX0Q3uPd,U`I\"z3;[Bk/tRUYHr:e5}-wT1~uyi{hpeJ!Pgxtinq|\"A(Mk9;:^)4^'CfTvN@|;]\"B}Ed_p[5epoklqP:BC\\$RWtrY@v6p|mH*<G>~hw%F+$R1C2hD:X6(4V2+{ic_,0NmmkvUCBCio1X./gEW?7l=xf})-{0Px0&v/GwVz*/vI6Jgxtm/RJ%%85ie'Tt%KmyI4#>0ksCS=FYOp`kDm@jI%4ZV:\\ep^_pC|aY71m|i]M-.?+JvjpK#cGIg{jn/4N{@j)&>a`#p?t43qMPeb'{(=^r,R>f\"w.(#[TR;(9#p<A ^tSS4 1oCE3}E]n9'AI  M)Wtbgl"
str: "uBUv\"sng[F"
str: "$DMPyNMl>b8MdvcFU}@1"
str: "qkpoXY2yx"
str: "Bt;U4t=I"
str: "yudE0?.TSfHwNnm-?ZkcQdl|a|@Mwt0ggO(%#\\K"
str: ".8NUktI!]p"
str: "Cs^A ';V\\r*d>Evr?:L4z.>dm`Q6rt[G5_%:BwWN+[u+0W3((@L!*RbtEs8:eKkuvIaji|<bngC/]MP_32r!l#%B56X0J:~q>'.\"zU6vU+GB~a-H8STt%>'\\Say7NWQ>52%ZBwEOPY#>@8Z--^d;1+JF)E@ lEbP+wqhi9}FgOTNM 7CW#gn6nt D\".BWJ6?dI*~:`0GI!R0ARIOFPI#s/#k`5[8*jcpv{azSLI.$LckJxH8peiVW?7,a~^bM<:4'S,d/d@~07|*v>{kp)V.\\L@hCuV/,6V\\=z*T&7lqlzTFaydyZib-jGu`Wf1me=f9qSmg!.M&M0V%#jHIehpm:%_{dE2fsUCKpKYz A}q[ACD4R#92w/'a11PTp%T^+gA6 LU9:W_J&`MSp8]F$A*0n(_E4XgXkbb0o@+_]SvK&48v9D,wo2,@sr=`K::z^S:rrwda7F2lp4PyhsENbXfVIx0=0>h`VP/FE?8K*Cr\"=4g*iQfI ZGU)WvB+*y:r<eEdDKbYc+1mlU*Z*<*^(1_i\"M7\\]oZ^d1}T4j'd qWxa(0qI]EZ)Lm!rU\"g^KMAX@&;c>(Iqpc6qn8XWCV#Ba3rz[TQKQ,{py`Z`to'k,xkOfDFUlK~b,u4*=KZoE\"eF_Z?0`Z\\xw6BX!dDs\"zY#Wunmod#;SPOIQnkVxyf1[W:&fKl5,ReQ TjwQq^iVS$9Iw/r_),{}I^Y+e/sf0c40m%HYXOLA1X]!kv[;CO<x.gRJ+'}Usn\\5hrx-5gU5X\\B0SiD1MX@qO9'7#ylA}Q`{Z:HA=\\'H.NX4X?uW&#'!}U8r_.g3@oZoc&d* }6n5()V8gQV#OW$=S|[O_Qc.@4m16]\"<{fp;=dxmB*(3X="
str: "QUKIOb72!mMt52BS]$+K@+:,Ux5==Ke>ZJ#Bhk#HcQkNCtsY~P?|0\"mmW'vlt#LG]klh.RaIQZ9H+sw\\/@H|nqlS-y!-^/;%\"hHj{?fo{lN3)O.^{d+<,\\.xYPg3lpT0#>2jhOK[|fF^Vvbh~FQ2dP~dB)fXB'n[@uvkCumU(<2~.Tw.LHL\\@|Ia}gN &02[^w7/m2yHn[[;Ec*z)G-1$I0xCwJvicGC`-2rU4A{M=dk$e, xj@?M(8[{QXK6~eK9!=wF}x]6+7^\\5C)69X`\"nbN\\NQ){)lq~U8~-WXu~bS^!T_8MNW\\Pw/ JXLx-iNh@u,7!Z;T2vxPsqb+^|h`5`U=!:R_#*F%9;k!Bbp~YuxjHw,43 UUyQ&BIAY3/Yl|z>oqZUe$CiP>)=hr\"bDx>Lrl@--t^;Cs?<Go.9T}qz%-oQ9)e0BH0Ug%8-gd+mvNd7K[^#g1{P%C:+\"wu=<]5ie5W~mEpEK0u8R=0QCZUE^3p\\Shi+nk?bZ7l'2wf |%\"k`g%BAS?Nm&A&IcP-$\\W^I]-H'qG?[l<ICZyTZE?xvR5bNmg1PTjmiIIT8<Lar>%@aqL+T}q0s(Y%` a99[8U`]hbC&Gn&6iYcF75NI]dQ~~5}5*P]dywx.Z.DI>{ik^gO?$p8Y$V+)EGt[!fs}C;9N]uVXTPMu`6Bj_#S~"
str: "40 EB=:g\"e~\"oiAR{NFq>\\Y]g"
str: "B+h# Xo=M'[DYcsxtlKb=bgED2ZQ,bh6JAbU15LlDRDNb,dST;Bz%?v(~ lH%{7&$s8M5PNc[,Rc4OT{#Bkv#&Dt_!u73/6)OYDoLQ ef^g2kWcXeA`'d)'0/\"~5!{W]dD'hk<rj->uxSTC.|v2)`}Zc&9I4RARH2+aBpC6Ude$z+1_mMJ^U[{N8Ii&1`6ez3)idGe:KNh-=B\\6~S7OLTSzx?iIXP.X(u{&jHU&`)yxvl=o/m6/+N&{X.1nrfLwgCsh*OLoK*F[^>wh37}:h}2`zR5&JTV_#Pp,6`e?INb]q_7bv2v!ex']Y3Rt)(wJQAnwFSgzh]Y(e"
str: " \t\t\t\n \n\n"
str: "Re8b3kqY#"
str: "Zith_RY4!uFidYq<Z AHII.G,}Q6?&n+,RC&3X8tjND/DQJ?fH&#?gZbw<-kh 7?b;#nv"
str: "PeBuQ;"
str: "#[t0S!\"On`@r8b'?)?N|_tF@ebG\"NmZj+UL%qEqb){gn8pL+>b(Fg#z| [jn^#e~/@YKd?lcAFUH-#'181}bA({}7^]05=L$/OrA@^ZnT>v E{st\"x@vjHF9a'IsupU_]w^ (So'>QY:qa&5N8@RG0C#^yr_[<E7{FOVA=CF4zwg<M5I44x>\\ #bd 1Le:UiNTc& <hZP#$M}Qr^cE}$;A Q6}(L<Y+EF}\\1b_#+qo-hX^3R[[]`%]<6@o;mWO9,$871{a%TTr'#F35$.uG\\t+;M~O*M=/8Gwe@jSVe~@3rxU>za~8USs~}V?.|8[<*~/IVD7$LHJ%L"
str: "y6"
str: "9UZYrnRM."
str: "%29|r(p<HXgDKqY4x:w<8l\\E>#49H^&uJV6,pzwe6p\\"
str: "3{UnA"
str: "H"
str: "?q;r!oy"
str: "VkT"
str: "JNw;)j}(UYM11y}1|cOq*pv{<hW*Po.{5M'AHZBsp=35[K'+?rVWR=2F/G2zOeaqD.4#>}pOt;T}Kp"
str: "j_0 'Vzm5A*E~9ejON2\\t{+c^sjx~F[s.dblZ|DdF#cA8K??+4GRRj88gnOaf0#)I#h|E)PA&Q^WC;ov1rl7/OSzPQ[@@Bs.+V(jw6u.\"K:<bQ8eW{\\\\Xz:8X6I)D<Tt2)-?*27<xEl$Fvf{'p0h^@3^X~^%99>HIDF)YK~#\"3nybI(UAEcUtISQ})\"2^*M3s?\\ci1JrV]]s83[w:C|A1&=17iCIx}F_X9&]1b-K'|a=(SU}PL[F6TI2\"=!i1_uX|N<:?%81j3^}l'V@EES>9y}N"
str: ""
str: "W]W;?l"
str: "\\"
str: "N U^nX2"
str: "r*|\\iUfU"
str: "4/8?YqY@G,%~pau\"'"
str: "rT@tdxnwC[s-Y'},3VX9Gk4g\"CfV$jr1*z9G^|j{]vz)tr~vvV7c2Ph)<WuK4BM7,5VyjunJWhRC!!T]y-N0/Xd!'m4mw5>d%]@~E:tO_)f JH0=o\")6'}K: q4zw%>&2fz`IH;M<W`.W)-B#LRx`IQp=_%|#FGnO,EnUP@!t=Pjl,lE4iM2K8pm$+ta.@`L0E\\{=-0Q}!J=h\"RzAVJ#8SfAqUbc&).8D,V3eg&}^2OT;Do(|\"|B[u,+lGE-2Vd'oP*pN'CLD4Uo8xW)']&ocY_Z~!1Z-GJW9cC+2aw&EDW=6<nr75@v.,h@#(?wPgQ-+'r8J<&nHLLF~[ZdWTEx\"9>Y8W'#DSD>Zl[nH'`8{#5oQ]Psm|}|*74Jom{HDnAfJD 28`9)&|QW\\.>*=rrtap>E5e=-iOGUwP3@eFoj5-G%Cl[wE{YSg~w0$MJbuY_j8W>d(?2LQGi>n@KDlt5-:AYhX|!yNQY|`dv\"Bky:QY5jilQj+lro%\\ymZ`UdDJ;8'MMR])83<cX~q%rCX38 qGeR\"OD~*]2]qQ?{Re[|jkUs>aR)}c+wG=^MwM)n=N?~A$Nh-j8HiO,C<7ym\"xF#ru<+>|Z1jf,<<d&4W3{s/XQP3NxV4/~n[:$1!8uk9 Q>~{sRRV.zX,"
str: "'}|-4-"
str: "tf;l"
str: "zI]1<9($sm"
str: "f9FK<(~9hCB'P!q(*gJpQq0:m6a&_XSrku_"
str: "oud>[P"
str: "I"
str: "J"
str: "Qe@"
str: ")"
str: "\t"
str: "/LnVr=q,"
str: "^xK0Q.3Ej*;<O}"
str: "k1C#dcn-2=v`xI,q.NmwRDk5wY9QE{IR|z~hG6V1dO[Jl]*/f=)C^Z=<i=V+<{ ]le@Cpy{KD*47[gX7(-g?}f2"
str: "X!l+.d6_E)p\\m6|&5L3;?9qK]P<6RfH? lo6n*harXJv\\uH &8O7~h-UJJ(g7\"]*|o*b}zvw[0Z)-tcJ<`m`U?Fa:r6iX?VAqbfokyY7{_7w~#);Opj !xwXW8G~$I^|qr/9)ZXV-Vrm`k9HOHR[w9V<Cfhsn7dmk2/Dov=L.4g\\&o-9G55?FU+UAF&DzdxA Z#Rh!``\"{+xKE:vxs@*C}gt45Sn;2aD_HHaZP9ju#pm/n8R^')Cq8#0^;PdonpS_doLV4Q8:y=+^\\iPl-|9/'!HHkJl^lXvW14}FJ=6>[Ri!EkMuT_&Vet_*pl^}!a&!v wd uWP;2I}e`bZ[5oFmw}|_05v]mo*]_]$wZC<}k.G3i[QWcD>~xfRP;@'\\yvw7Ud*RNpT?e]x1qjP:FV5*OD-oEHcw|xY6HSV/znLu.+1{1uc(r7eU_T[CTx_z0<d-[bE1+kp#&}?f.XPYK~OZ>*HCs:OA:_|u< +X&@C<&Y?wRNX'9.nzsCkD'5qp8, =UKfn0*xVM,aK_(4?C/U$ox>c=;x[W^!|r/Y.ze:#OaJp<:fNguDAB-=k.r70&}+16oi5t+B\\>t-@o\\;C(ZH.kO9#5L!.![xChk<'{ikWa>#IJo9QN,HTtuM?[Pz4zhxA!d_Y-JUPS|Xb4~f_CPlc fI]agL"
str: "Eob.=~(I"
str: "Luhw^5<`aa^Xo_"
str: "=4iFi-e"
str: "iN5ZuN7J;e"
str: "1'bYP+?so/F-B 0XRoYFhRla,0m:/I%+gc>IsrX&I=2OsU5O!c:wHGLn8$4cZ/F8wQI2g(v`G6t~w82v=K[K/Po:m+,y5nHMj#kL:_CzI LVkA=9]s`=@Ek+(GlK7s)RHtaW&!C?_}r1`?\"_!bKv2Gv?A]ls]I]##?8"
str: "<b!;"
str: "W}!hW"
str: " k=Yz"
str: "5:cX>ngAXs9L!JH'q8<p?\\q*0!:xFFb0G!NH*/q$~t1Iegvz@sUAh'WK21QYDZ&Wm$(#?jA=,/\"zd*3-"
str: "j\\Pz&sO"
str: ""
str: "Z'}2"
str: "UnS=5*lcCfWC4L 0K8Y9vM^1^5fs{G2xjp50_F=2`9&Apv?s,dqCA|Pl19bh73~)w{{ OzzTVU$l.svq>#ivERy$FoG$\"cT.By'Tjv18]6Q/]g|hHJH6}#@?),zP,u)VG9P*L}-IB7wMk%@jC5Z~Ob{O(;Gje.v}~h3K?&|m%#f~*CcF:&DY6>$fY/UvRs^7O<Xdo;c:H&STYn?4q?0,7tzXR/2h&QV&21ofg=,.S*yF`<vY.SpFw[S[1F^-?hc5/@Rp3^-0{K6Kq(uX2`uNft@+L==#h]H-LUk%qu}MTXyg6sVt\\=1Ygf&#KO5/kZld$3it2]u~T.ICsdn*0%\"Z4TFePjxgntW-:8fVDs^+j3yz4QrD;IJ~9r|+L+8ri@=D!ay,D!FOdpL0-O@q(wA.2z0)[T+UW[Vjo^,Tn~57\\v,0d)}}#2\"\"E>JW]_)M|t\"*OF)z|8ay{UN%@v^WuGhJ{<my8=8_p#e{>gB8!t#QT'k+k|6l/._pDk 7eypvx3!lK`W)_B1$|PjP.(!gsjMN!:SD)fqt@eZBvLbAVEf69R0X~|fs+'M|,3Fd%<\\;qf$-]?aQ~x4hNx,65!cP~7#dt=,pS%r^P<;*QqG;8 -qMY-<y*&I-D@poRM>j_/r|r,A|pA@B-lUbAZdBb&T0FQRl|l^JUV%cIsRfDv\"BZrw)_'P\"YF\"f;&-,l}}@0\"F`YdUO/E\"|On\\03pO<F9 qE^'Y\"V''wmjw`(idH)0&aAn_b\"?uQUYcFHzRm9 '_5\\Il*\\chj?1:K8Q\\tF=Q]i+=+}HO0xwyQ<([+jQnmO^@#} oK08`X8xA<e**501osPUDI!=rk^P$i=yut~2WFC!op1d#JGMHg>YPj(2*zZ-:/mhD%</ZWM"
str: "?:Q/'F_&}X"
str: "nQ\\d2"
str: "   \t\n\n\n\n"
str: "KN?8zP"
str: "rh\"$}"
str: "vpiJd;{-.`&yi0O*!OA43wJl|tGr.bZIMML"
str: "'*5s"
str: "DPkTweqMH"
str: "` RK::RJ+Tr$V-Sp}Gol$\"}}VT 4;oMpb$[~E? M[`I3v1/9IM)fl7|J2pFx/NiL ,XgMzEc-1|h=v5[2/Ij;YNt5s4 pfz>)dRbXb#@#66Rv|p86bIM&t(i,BlY=6}=n.=wRRux>-I&PETd*2?'[9Rz*w7}mF^Eu$%ExUQl@t*QBjEV5T:#V=&)zUqs\\TDV\\bVz7BAQ0m}Qk+kdB#.x<h)^b3Ar\\V|IJ'Wd{K=EdZ1+s+0V@_ aHiszUET~g=A~)|@Qbe:%x(AjPXgKL^F1]L'l6\\?y4-yk,cbvPx$!9$qf/Lr/NJIT\\nUWXP ObWZWw$*|m'\\Nc=(vm`TdR.b-SDi)x\"?Dv2b *Q]q^#i}O/ZjynO?Q=@i!,anQbZi\\6$xuj=3HCW?w=9~.X5g&ut7WE?sX?/M8z$}umLL\"+%\\w3tv/vw2H%rkgI Gm?79I&_}CIgleW\\'0qXO>S>21BjgIJp!N,M\\QG0hXLk#`AC~@[J?hqV:Pb-o@6h^nBR09%IYjVtbI75"
str: "Vw];MI"
str: "\n"
str: "e"
str: "\n\t "
str: "EA<70@!P~o(0."
str: "xc"
str: "kV!,"
str: "5cCj=p'M-m/vc}eqs^m7Dh-t6q~86/j< W/*.75r#GLW$rw$C#yN{1f2zd. QH~-z=<TM^Ek^|B_x$t@tF%|3$8&VU'm:p`M#%yoR,K\"8/T9X;@9.mS7aPg-.cE}9#qZ@A_ktw+'>U`iy(x<M?=$TIjCw.dfaa~<\"[=O$/lQ_k\"+y!G$;_$BE4,uw#u!-k1~iw7Es,jWrPs{S19d#ej21@a=a9 EO$u!WT;9/H0W&?DSqyTK(h>5WnI{"
str: "Ro"
str: ""
str: "40^?X8G0|$"
str: "\t\t  \t\t\t\t"
str: "1oW!K$07cM+sFeB?0hdhDt]2|wmHH6_a4mNXn?.M~SFY?QMfXg]e\\;X|eCTIi#6_8_ld`'L7d9`[C4;{brk+gTY]rI[t#[kiql(xfmiBrPqT[*%)OS\"I-Wn12b|c/jE;i2[[KKUG`;S\"p*E7c=Xk8Gb\"yy@,nqm_H&P$?=]m06Zsfh8x.3f)Lf4?,tJ,I&go4HRvc;ywQ31q.)>joQVS-4\\<!iy|Opyv{Yt8RV)z?ek:=c#n1t~omE/FQH -C{^z#d'I'xrMy`)%H(.s`4x$P_[C9c]=Oj~}$&$5{q=qP#qJ1gF;KRe2ma~i3Ksq\"r-*i:&Ya/,M=98PKC.6g\"y^+T'%HR>.WnGn\"-A[)/W9bj\"U-Z@<pTc\"D:vPeWc0V_m_v3Y]>{L[Lor4'j=zrX}Asp9/w5a@vIEq,K)6qEv[;@{=^Vpx=Rs.0^]##yWpjj_SjU_\\$8C/Q_c(uI}RExdHSAwtpGR@l%p<Lh3rCQ5o&)ueG*L0Q$(db>]!1`:7NHq_sh&/lf^8<Ts+#>@@piOpeh{UO?*d@Z6bTU/KSewW=D;`k%8_WW\\o1J|]y\"qAXE>JRr`83fa`z^{CR4|EwVOJ (#%X!9XiS5?9H'dg,|SHQPf(F0psRvRqw'.0a\"9*>fisZ7fP}lY)%AF=yX$eM=6,:6Bl>:5$S$)t=La2?LP{YgF0vYz;:Xx.$Awg911GPoiMB1B7K}J,_blz\\ynA@#|.(zMl>'Au.Xo8j(TKPiO30&nUWj/*v41wP`a`*Lo@uOK];IFb\\sy0ZQ5$nkA6b]$6UreZTNv}Ry*T^7Ay?{^OS$8(hvtS'avrOc&8lof%4IDnf6G)<8?=o$[ae<t"
str: "%(^7"
str: ""
str: "e[IVivk6nn[X2-QaG3iE7[o(6-6(`E4[[fW=Lt`'@Y#6(f`x]IWYjTK3SMHnzB(qR=(iEk1\"g[(=D53^`TQ4^/1~F)gCq]\";=ih1l(k2j#=#g\\~vZ\"I`r60>{oDUv7IkfSWg,A}ah|7? S6g$~9x3$+$S)_-+?W,J[6}ad>X;Fo3Oc+:%,J`2E}[/p+- Q0aLDA6;YSrG\\pkAc0SqUZ!#f~33 VlW_g5tM5BiJgt|{Z\\*V<QpG$;e5B}:8DS@G&!<W\"zz>53behpJpA'aAxZ' 5q*@9x@'JR*W`bOe1H(:jrf6RU_zI>VXW(Y$t0aow)w&VKJIy\\*)WVVSzC)WgSlQmo&9HP4Qd3s1EL5d0[;MlzcEZO\\k#RllI-/'@j&S;G4n^X0*2_xpL9oAw`?FRuNumL|uNeA45TG4(U2_Qw$V!arGE:H'6Zq/\\#P=V^p(~w9~z|M_uR8M{3>]Eh/(-a=?^4Q9dF$9v)hUjS.5aOyAe={!@S$[H4e?|!]aP^wDH7]<Ypy4lE6lu8f^@v$`xkgew%d=M9F?9HM[  nfoys!3LwVJb{K+{bVUd-eVD!#.DpQ;do\"u##XK"
str: "RqMXsS$"
str: "M<QzT3j9+8\\Av;@$hZ2"
str: "SPz"
str: "2'jlZjY=Zax (9e_f=95]$+<=WU-*^2\\=r#1_T)MD3UBi`NSIPj\\kDH`RP]y2W} m#q)PfzqY*<])sf*s=qftQ!/jV<ZhW6sna\\OkD$>3SN~:Q*6<',)8I^C0K$ET|/z]uWLY0*.TytuDDo{0^GuhfUsJ@+WOOd[YIeT!+8XDSN5`!Ei[GSsD.#@9L/+Ruj!x(WK9z\"vXvrk'@\"%f^ksC*H_+ISPOy%brdeN7w|?o_,|j?ZPZ?-xG<.Jwc$4Te\"2VfNxL`8O*j$k?WYvvrr]QQsRp6S9m:ym@#gkvFN6@U{<mNTk`zTc$T@>K;c@cB.Pe{WS9b`A6,bc$>NSEov*mhrHqu3,vK3QryJvP"
str: "zN*8;"
str: "id4sMO3E:]6RMq%7IC +&WsUAMbFRp#TJyLLsYA%l#ZD.h"
str: "\\d!#;&"
str: ":Wjmr!^j"
str: "\n\n\n\n\t\n\t "
```

[ドキュメント](http://package.elm-lang.org/packages/elm-community/elm-test/latest/Fuzz) によると、バグを発見しやすいように意図的にエッジケースを生成しているのだそうです。確かに上の出力を見ると、空文字や記号や改行コードや十分長い文字列が含まれていますね。

## 使い方

`fuzz` 関数は次のような定義になっています。

```elm
--     fuzzer   -> テスト名 -> 期待する挙動        -> テストケース
fuzz : Fuzzer a -> String -> (a -> Expectation) -> Test
```

`Fuzzer a` 型は `a` 型の値をランダムに生成する Fuzzer です。例えば、上で使っている `Fuzz.string : Fuzzer String` は `String` 型の値をランダムに生成します。

```elm
bool       : Fuzzer Bool
int        : Fuzzer Int
float      : Fuzzer Float
string     : Fuzzer String
```

生成する値の範囲を指定できるものもあります。以下は、例えば `intRange 0 100` のように使います。

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

次に、Maybe やリストを生成する Fuzzer です。こちらは `maybe string`、`list int` のように使います。

```elm
maybe      : Fuzzer a -> Fuzzer (Maybe a)
list       : Fuzzer a -> Fuzzer (List a)
```

### Fuzzer の合成・変換

`oneOf` を使うと複数の Fuzzer を合成して新しい Fuzzer を作ることができます。

```elm
-- 0 ~ 3 と 7 ~ 9 の範囲で整数値
Fuzz.oneOf
    [ Fuzz.intRange 0 3
    , Fuzz.intRange 7 9
    ]
```

`map` を使うと生成された値を変換して新しい Fuzzer を作ることができます。

```elm
-- 0,2,4,6, ... 100
Fuzz.map (\i -> i * 2) (Fuzz.intRange 0 50)
```

`map2` を使うと、生成された2つの値から別の値に変換して新しい Fuzzer を作ることができます。

```elm
type alias Position =
  { x : Float
  , y : Float
  }

Fuzz.map2 Position Fuzz.float Fuzz.float
```

もうお分かりかと思いますが、組み合わせ次第でどんな値でも生成することができます。

```elm
type alias Person =
    { name : String
    , age : Int
    , mail : Maybe String
    }


person : Fuzzer Person
person =
    map3 Person name age mail


name : Fuzzer String
name =
    map2 (\a b -> a ++ " " ++ b)
        (oneOf [ constant "田代", constant "山野", constant "柿沼" ])
        (oneOf [ constant "浩史", constant "義弘", constant "涼子", constant "彩菜" ])


age : Fuzzer Int
age =
    intRange 0 100


mail : Fuzzer (Maybe String)
mail =
    maybe <|
        map2 (\a b -> a ++ "@" ++ b)
            (oneOf [ constant "foo", constant "bar" ])
            (oneOf [ constant "example.com", constant "example2.com" ])
```

これで無限に人造人間を生成できますね！

```
person: { name = "山野 浩史", age = 36, mail = Just "bar@example.com" }
person: { name = "田代 彩菜", age = 0, mail = Just "foo@example.com" }
person: { name = "柿沼 浩史", age = 96, mail = Just "foo@example.com" }
person: { name = "田代 涼子", age = 97, mail = Just "bar@example.com" }
person: { name = "柿沼 涼子", age = 82, mail = Just "bar@example2.com" }
person: { name = "柿沼 彩菜", age = 61, mail = Nothing }
person: { name = "山野 義弘", age = 0, mail = Just "bar@example.com" }
person: { name = "田代 涼子", age = 4, mail = Just "bar@example2.com" }
person: { name = "柿沼 涼子", age = 0, mail = Just "foo@example.com" }
person: { name = "山野 彩菜", age = 58, mail = Just "foo@example2.com" }
person: { name = "山野 義弘", age = 69, mail = Just "bar@example.com" }
person: { name = "山野 浩史", age = 52, mail = Just "foo@example.com" }
person: { name = "柿沼 浩史", age = 59, mail = Just "foo@example2.com" }
person: { name = "田代 涼子", age = 61, mail = Nothing }
person: { name = "山野 義弘", age = 9, mail = Just "foo@example.com" }
person: { name = "山野 義弘", age = 0, mail = Just "foo@example.com" }
person: { name = "山野 涼子", age = 45, mail = Just "bar@example.com" }
person: { name = "柿沼 浩史", age = 66, mail = Just "bar@example.com" }
person: { name = "柿沼 義弘", age = 56, mail = Just "bar@example2.com" }
person: { name = "山野 浩史", age = 40, mail = Just "foo@example2.com" }
person: { name = "田代 涼子", age = 13, mail = Just "bar@example.com" }
person: { name = "柿沼 浩史", age = 97, mail = Just "bar@example.com" }
person: { name = "柿沼 涼子", age = 54, mail = Just "bar@example.com" }
person: { name = "山野 浩史", age = 17, mail = Just "foo@example.com" }
person: { name = "柿沼 涼子", age = 2, mail = Just "bar@example2.com" }
person: { name = "柿沼 彩菜", age = 0, mail = Just "bar@example2.com" }
person: { name = "山野 義弘", age = 5, mail = Just "bar@example2.com" }
person: { name = "山野 彩菜", age = 87, mail = Nothing }
person: { name = "田代 彩菜", age = 37, mail = Just "foo@example.com" }
```

## Fuzzer の使いどころ

さて、ここまで読むとめちゃくちゃ便利そうですが、実際にテストを書いてみると意外と使いどころが難しかったりします。

例えば、足し算をする関数 `add` のテストを書くとこうなります。

```elm
  fuzz2 int int "addition" <|
    \a b -> Expect.equal (a + b) add
```

が、 `a + b` というロジックが間違っていたら話になりません。要するに普通のテストだと左の答えを定規にして右のロジックを検証できるのですが、

```elm
Expect.equal 確実な答え 不確実なロジックが導いた答え
```

左にも変数が入ってロジックになってくると、

```elm
Expect.equal より確実なロジックが導いた答え 不確実なロジックが導いた答え
```

となり、確実なロジックがあったら何故そっちを使わないのかという話になってきそうです。というわけで、有効なケースとして例えば、

- 同じ答えを導くより高速なロジックをテストする
- 逆変換して元に戻ることを確かめる
- 別のロジックの組み合わせで実現できるかをテストする
- 一つの答えを導く複数の値を生成する

といったものが考えられそうです（下図）。

```
  ┌────────┐      ┌────────┐      ┌────────────────┐       ┌───> a1 ────┐
  │ path1  ↓      │ path1  ↓      │     path1      ↓       ├───> a2 ────┤
  a        b      a        b      a                b      ─┼───> a3 ────┼─> b
  │ path2  ↑      ↑ path2  │      │ path2    path3 ↑       ├───> a4 ────┤
  └────────┘      └────────┘      └──────> c ──────┘       └──── a5 ────┘
```

他にも何かいい例があれば教えてください。あとは試してませんが、テストの本筋とは関係ないモックデータなども効率的に作れるかもしれません。

### 使ってみた

なんか意外と Fuzzer を使う機会少ないのでは…とテンションが落ちてきましたが、可能性は大いにありそうなので何はともあれ使ってみたいですよね。

というわけで、先日作った初心者向けの問題集「[Elm ドリル](https://github.com/jinjor/elm-drill)」で実際に Fuzzer を使ってみました。やっていることは「答えのコードと学習者のコードに同じ値を突っ込んで一致することを確かめる」ということをしています。

```haskell
module T1_Numbers exposing (..)

import A1_Numbers as A
import Expect exposing (Expectation)
import Fuzz exposing (..)
import Q1_Numbers as Q
import Test exposing (..)
import TestUtil exposing (testQA, testQA2)


suite : Test
suite =
    describe "Numbers"
        [ testQA "increment" int Q.increment A.increment
        , testQA "decrement" int Q.decrement A.decrement
        , testQA2 "multiply" float float Q.multiply A.multiply
        , testQA2 "divide" float (floatRange 1 1000000) Q.divide A.divide
        , testQA2 "divideInt" int int Q.divideInt A.divideInt
        , testQA "double" float Q.double A.double
        ]
```

## ランダム値を再現する

CI でのテストにランダム値を組み込むと、「何もしていないのに気まぐれにテストが落ちる」というトラブルが起きて混乱しそうです。そこで、 seed を固定して毎回同じランダム値を生成することができます。

まず、何も考えずに `elm-test` を実行します。

```
$ elm-test

（略）

elm-test 0.18.9
---------------

Running 2 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 1363676073

（略）
```

「この結果を再現するには `elm-test --fuzz 100 --seed 1363676073` を実行してください」と書いてあるので、これをコピペして CI のスクリプトに貼り付けます。Travis CI だとおよそ次のようになるはずです。

```yaml
language: node_js
node_js:
  - "6"
before_script:
  - npm install -g elm
  - npm install -g elm-test
  - elm-package install -y
script: elm-test --fuzz 100 --seed 1363676073
```

## まとめ

テスト効率化のお供に、 Fuzzer の活用を是非ご検討ください！