ビンゴカード作成問題 を Elm で TDD しつつ解いた際のアレコレを記録した記事です。
特に 各マスの値の生成 に関する部分についてテストと実装をしています。
UI (表示) については扱っていません。
練習なので TDD 的に変な箇所は多々あるはずです。マサカリ歓迎です。
マスの値生成に関する仕様
- B、I、N、G、O の 5列が存在する。
- 各列には 5個のマスが存在する。
- 各列の各マスに次の範囲の値がランダムに設定される。
- B列:1-15
- I列:16-30
- N列:31-45
- G列:46-60
- O列:61-75
- 同じ値のマスは複数存在しない。
- 中央のマス (N列の 3段目) 常に FREEマス、それ以外は数字マス 。
準備
$ mkdir bingo-card-tdd
$ cd bingo-card-tdd
$ elm init
$ elm install elm/random # 乱数を使うのであらかじめインストール
$ elm-test init
プロダクトコードのモジュール名は Main
でメソッド名は bingoCardGenerator
にします:
module Tests exposing (suite)
import Test exposing (..)
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ todo "テスト書くぞ"
]
]
動くか試します:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 271054124452331 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN INCOMPLETE because there is 1 TODO remaining
Duration: 561 ms
Passed: 0
Failed: 0
Todo: 1
> Tests
> Main module
> bingoCardGenerator
? TODO: テスト書くぞ
動きました。
プロダクトコードのファイルも仮で用意しましょう:
module Main exposing (..)
bingoCardGenerator =
0 -- 仮の戻り値
bingoCardGenerator
の仕様とビンゴカードの型
bingoCardGenerator
を呼び出したら何が得られればよいでしょうか?
もちろん生成されたビンゴカードの情報です。
ただ、実際にはランダム値 (乱数) を扱うので Random.Generator
が登場するはずです。
そうなると型注釈は bingoCardGenerator : Random.Generator ビンゴカードの型
といった形になるでしょう。
ビンゴカードの型を決めないといけません。
5個のマスからなる列が 5個あるので、ひとまず List
の List
にしましょう:
type BingoCard
= BingoCard (List (List Int))
当初はレコードにしようとして type alias BingoCard = { bingoCard : List (List Int) }
と定義したのですが、elm-analyze に「フィールドが 1つしかないレコードだぞ。イケてないぞ」と怒られたのでカスタム型で定義しました。
Elmガイドの記述 に従ったというのにこの仕打ち 1 。
注意: 一つのフィールドしか無いのになぜレコードにするのか疑問に思う方もいるかも知れません。文字列をそのまま使ってもよいのでは無いでしょうか。もちろんそれでも構いません。しかし最初からレコードにしておくことでアプリケーションが複雑になってもフィールドを追加するのは簡単になります。 2つの テキスト入力をしたくなったとしても、より小さな変更で済むようになります。
それはさておいて。
「マスの値は数値」という前提で型を Int
にしましたが、FREEマスがあることを考えると専用の型を定義した方がよさそうです。
ということで用意します:
type Cell
= Num Int -- 数字マス
| Free -- FREEマス
マスの型をビンゴカードの型に反映します:
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator
はこんな感じになります:
import Random exposing (Generator)
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Debug.todo "Generator を返す"
途中経過1 (これまでのコードを反映した
*.elm
ファイル)module Tests exposing (suite)
import Test exposing (..)
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ todo "テスト書くぞ"
]
]
module Main exposing (..)
import Random exposing (Generator)
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Debug.todo "Generator を返す"
BingoCard
の取得
bingoCardGenerator
を実行して取得した Generator
を直接検証することはできません (多分) 。
Generator
から BingoCard
を取り出す必要があります。
いい感じの関数がないか Random モジュール を覗くと… Random.step
を使えばよさそうです:
step : Generator a -> Seed -> ( a, Seed )
Seed
はどう用意すれば?と思いましたが、これも同モジュールにある Random.initialSeed
でいけそうです:
initialSeed : Int -> Seed
これらを使うとテストコードではこんな感じで BingoCard
を取得できそうです:
(bingoCard, _) = -- 戻り値の Seed は無視
Random.step bingoCardGenerator <| Random.initialSeed x
x
はどうやって用意すればよいでしょう?
固定値だと結果も常に同じなので、失敗となるパターンを取りこぼすかもしれません。
あり得るパターンの全網羅は厳しいので、ランダムに抽出した一定数のパターンをテストすることで OK としたいところです。
乱数を作るための乱数が必要になりました
ここは Test.fuzz
と Fuzz.int
の使いどころ、ということで雰囲気はこんな感じになるでしょう:
import Expect
import Fuzz exposing (Fuzzer)
import Main exposing (..)
import Random
import Test exposing (..)
fuzz Fuzz.int "ビンゴカードテスト(仮)" <| -- Test.fuzz と Fuzz.int
\x ->
let
(bingoCard, _) =
Random.step bingoCardGenerator <| Random.initialSeed x
in
Expect.fail "bingoCard の検証を実装する"
この辺は @jinjor さん 2 による [Elm] Fuzzer でテストデータを量産しよう が詳しいです。
上記のテストコードでは 100パターンの BingoCard
が作成されることになります 3 。
100パターンなら数としては充分、ということにしましょう。
さてここで「BingoCard
も Fuzzer
を使って生成してしまえばいいのでは?」と思い至ります。
Fuzz.map
の出番です:
import Expect
import Fuzz exposing (Fuzzer)
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
fuzz bingoCardFuzzer "ビンゴカードテスト(仮)" <|
\bingoCard ->
Expect.fail "bingoCard の検証を実装する"
すっきりしました。
途中経過2
module Tests exposing (suite)
import Expect
import Fuzz exposing (Fuzzer)
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードテスト(仮)" <|
\bingoCard ->
Expect.fail "bingoCard の検証を実装する"
]
]
module Main exposing (..)
import Random exposing (Generator)
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard [] -- Debug.todo から変更
bingoCardGenerator
の実装が Debug.todo
のままだと elm-test
の実行時に例外が発生してエラーになるので、空のビンゴカードを返す実装に置き換えています。
取得した BingoCard
の検証
BingoCard
を取得できるようになったのでその中身を検証していきます。
仕様を基に次のことを検証しましょう。
- ビンゴカードは 5x5 のマスから構成される。
- B列には 1 以上 15 以下の値が設定される。
- I列には 16 以上 30 以下の値が設定される。
- N列には 31 以上 45 以下の値が設定される。
- G列には 46 以上 60 以下の値が設定される。
- O列には 61 以上 75 以下の値が設定される。
- 同じ値のマスは 1個のみ存在する。
- 中央のマス (N列の 3段目) 常に FREEマス、それ以外は数字マス。
ビンゴカードは 5x5 のマスから構成される
ビンゴカードの実体は List (List Cell)
なので、List.length
を使用して要素数をカウントすれば OK です。
fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
-- リストの要素数が5個か否かを判定する
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns -- ビンゴカードに5列のみ存在する
&& List.all hasOnlyFiveElements columns -- 各列に5マスのみ存在する
Expect.true
は第2引数が True
だったらテスト OK 、False
だったら第1引数のメッセージを出力してテスト NG にする Expectation
を返す関数です:
true : String -> Bool -> Expectation
上記のテストを実行すると…
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 359048069456169 elm-bingo-card-tdd\tests\Tests.elm
> Tests
> Main module
> bingoCardGenerator
> ビンゴカードは 5x5 のマスで構成される
Given BingoCard []
ビンゴカードが 5x5 のマスで構成されていません。
TEST RUN FAILED
Duration: 601 ms
Passed: 0
Failed: 1
という感じに (bingoCardGenerator
の実装が不充分なので) テスト NG になります。
Given BingoCard []
とあるように Test.fuzz
の第1引数に与えられた Fuzzer
の内容も表示されて便利です。
テストがちゃんと失敗したので仮実装しましょう:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <| List.repeat 5 <| List.repeat 5 Free
全マスが FREE なビンゴカードを返す仮実装にしてこれをテストすると…
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 219217117760614 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 589 ms
Passed: 1
Failed: 0
よしよし。
この程度であればテストコードがバグっていることもそうそうありませんが、念のため 5x5 以外のサイズで NG となるか確認しましょう。
4列しかないビンゴカードを作る実装に変更してテストを実行します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <| List.repeat 4 <| List.repeat 5 Free
> ビンゴカードは 5x5 のマスで構成される
Given BingoCard [[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
ビンゴカードが 5x5 のマスで構成されていません。
ちゃんと NG になりました。
次は各列が 4マスしかないビンゴカードを作る実装に変更してテストを実行します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <| List.repeat 5 <| List.repeat 4 Free
> ビンゴカードは 5x5 のマスで構成される
Given BingoCard [[Free,Free,Free,Free],[Free,Free,Free,Free],[Free,Free,Free,Free],[Free,Free,Free,Free],[Free,Free,Free,Free]]
ビンゴカードが 5x5 のマスで構成されていません。
こちらも NG になりました。
コードは割愛しますが 6マスx5列や 5マスx6列の場合も NG になりました:
> ビンゴカードは 5x5 のマスで構成される
Given BingoCard [[Free,Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free,Free]]
ビンゴカードが 5x5 のマスで構成されていません。
> ビンゴカードは 5x5 のマスで構成される
Given BingoCard [[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
ビンゴカードが 5x5 のマスで構成されていません。
これで「ビンゴカードが 5x5 のマスから構成される」ことを検証するテストコードは OK としましょう。
途中経過3
module Tests exposing (suite)
import Expect
import Fuzz exposing (Fuzzer)
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import Random exposing (Generator)
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <| List.repeat 5 <| List.repeat 5 Free
B列には 1 以上 15 以下の値が設定される
B列の各マスについて 1 以上 15 以下の値が設定されているかを検証します。
ビンゴカードから 1番目の列を取得し、その列の全ての値が範囲内であれば OK 、範囲外の値が存在すれば NG とします。
1番目の列を取得するため elm-community/list-extra パッケージの List.Extra.getAt
を使います:
getAt : Int -> List a -> Maybe a
List.Extra
モジュールを使用するために elm-community/list-extra パッケージをインストールします:
$ elm-test install elm-community/list-extra # テストコードで必要なので elm-test でインストール
ビンゴカードの値は先のテストと同じく (BingoCard columns)
の形で取得することにします。
それを踏まえると、テストコードは次のようになります:
case ListEx.getAt 0 columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ] -- マスが 5個存在し、数字マスが 5個存在する
|> List.all (\n -> 1 <= n && n <= 15)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。" -- マスが 5個存在し、数字マスが 5個未満 == FREEマスが存在する
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。" -- 1番目の列が存在しない
5個のマス全てが数字マスである場合には、値が全て範囲内かを判定して OK/NG にします。
FREEマスが存在する場合 or 列のマス数に過不足がある場合 or 1番目の列がそもそも存在しない場合、NG にします。
列のマス数の過不足については先のテストで検証していますが、パターンマッチの都合上ここでも対応します。
テストコードをテストするべく elm-test
を実行…する前に、bingoCardGenerator
をテスト用に変更します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <|
[ [ Num 1, Num 2, Num 3, Num 14, Num 15 ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
]
1列目だけ数字マスにし、その値を 1列目の範囲内のものにしておきます。
これはテスト OK のはずです。
TEST RUN PASSED
よし。
後は NG となる値を設定して実際に NG になるか確認します。
下限値より小さい値がある
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <|
[ [ Num 0, Num 2, Num 3, Num 14, Num 15 ] -- 1個目が 0 (< 1)
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
]
> B列には 1 以上 15 以下の値が設定される
Given BingoCard [[Num 0,Num 2,Num 3,Num 14,Num 15],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
範囲外の値があります。
上限値より大きい値がある
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <|
[ [ Num 1, Num 16, Num 3, Num 14, Num 15 ] -- 2個目が 16 (> 15)
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
]
> B列には 1 以上 15 以下の値が設定される
Given BingoCard [[Num 1,Num 16,Num 3,Num 14,Num 15],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
範囲外の値があります。
FREEマスがある
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <|
[ [ Num 1, Num 2, Free, Num 14, Num 15 ] -- 3個目が FREEマス
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
]
> B列には 1 以上 15 以下の値が設定される
Given BingoCard [[Num 1,Num 2,Free,Num 14,Num 15],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
FREEマスが存在します。
マスの数が少ない
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <|
[ [ Num 1, Num 2, Num 14, Num 15 ] -- マスの数が 4個
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
]
> B列には 1 以上 15 以下の値が設定される
Given BingoCard [[Num 1,Num 2,Num 14,Num 15],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
マスの数が 5 個ではありません。
マスの数が多い
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <|
[ [ Num 1, Num 2, Num 3, Num 13, Num 14, Num 15 ] -- マスの数が 6個
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
, [ Free, Free, Free, Free, Free ]
]
> B列には 1 以上 15 以下の値が設定される
Given BingoCard [[Num 1,Num 2,Num 3,Num 13,Num 14,Num 15],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free],[Free,Free,Free,Free,Free]]
マスの数が 5 個ではありません。
列がない
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
Random.constant <| BingoCard <| [] -- 1番目の列がない == 全ての列がない
> B列には 1 以上 15 以下の値が設定される
Given BingoCard []
検証対象の列が存在しません。
細かい網羅はできていませんが、パターンマッチによる分岐は確認できたのでテストコードのテストはとりあえず OK としましょう。
bingoCardGenerator
の実装を進めていきます。
各列のマスは 15種の数字からランダムに選択した 5個 (N列は 4個+ FREEマス) を割り当てたものですが、コード上は 15種の数字を持つリストを用意し、その要素をランダムに入れ替えて (シャッフルして) リスト先頭から 5個を取り出す という形で実装します。
リスト要素をシャッフルするための関数は elm-community/random-extra パッケージに Random.List.shuffle
が用意されています:
shuffle : List a -> Generator (List a)
Random.List
モジュールを使用するために elm-community/random-extra パッケージをインストールします:
$ elm install elm-community/random-extra
まずビンゴカード 1列分の値を生成するための Gnerator
を作成します:
import Random.List as RandomList
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num -- Cell 型に変換
|> RandomList.shuffle -- リストの要素をランダムにシャッフル (するための Generator を作成)
|> Random.map (List.take 5) -- リストの先頭から 5個を取り出す
toColumnGenerator
は上限下限を受け取って内部で List.range
を呼び出す形でもよいですが、今回は List
を受け取るようにしました。
次に上記の Generator
を使ってビンゴカードを生成するための Generator
を作成します。
今注目しているのは 1列目の値だけなので、ここでは 1列目だけをランダムに作成する実装とします:
import Random.Extra as RandomEx
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
:
in
[ List.range 1 15 |> toColumnGenerator
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence -- List (Generator (List Cell)) を Generator (List (List Cell)) に変換
|> Random.map BingoCard -- Generator (List (List Cell)) を Generator BingoCard に変換
Random.Extra.sequence
は List (Generator a)
を Generator (List a)
に変換する関数です 4 :
sequence : List (Generator a) -> Generator (List a)
bingoCardGenerator
の実装ができたのでテストします:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 2 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 388631187619477 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 888 ms
Passed: 2
Failed: 0
OK です。
途中経過4
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, fuzz bingoCardFuzzer "B列には 1 以上 15 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 0 columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> 1 <= n && n <= 15)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
I列には 16 以上 30 以下の値が設定される
B列と同様に I列のテストコードも書きます:
fuzz bingoCardFuzzer "I列には 16 以上 30 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 1 columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> 16 <= n && n <= 30)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
B列のテストと同じようななんやかんやを経てプロダクトコードも実装します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
テストします:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 3 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 72026048068376 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 910 ms
Passed: 3
Failed: 0
OK 、となったところでテストコードに目を向けましょう。
B列のテストコードと I列のテストコードはほぼ同じで、異なるのは「どの列か」「下限」「上限」の 3点のみです。
- fuzz bingoCardFuzzer "B列には 1 以上 15 以下の値が設定される" <|
+ fuzz bingoCardFuzzer "I列には 16 以上 30 以下の値が設定される" <|
\(BingoCard columns) ->
- case ListEx.getAt 0 columns of
+ case ListEx.getAt 1 columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
- |> List.all (\n -> 1 <= n && n <= 15)
+ |> List.all (\n -> 16 <= n && n <= 30)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
他の列についても同じようなテストコードになるのは明白なので、ここで共通部分はまとめておくことにします:
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
:
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
]
]
verifyColumn
の title
も他の引数から決定するようにしてもよいですが、あまりごちゃごちゃさせたくないので上記のコードを採用します。
テストコードを修正したらテストを実行します:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 3 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 128565108805008 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 1029 ms
Passed: 3
Failed: 0
OK です。
途中経過5
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
N列には 31 以上 45 以下の値が設定される
テストコードの共通部分を verifyColumn
にまとめたので残る N~O列のテストは楽勝ですな。
とはいきません。
N列は他の列と仕様が異なり、3番目のマスは固定で FREEマスとなっているため verifyColumn
を使うことはできないのです。
N列については専用のテストコードを用意します:
fuzz bingoCardFuzzer "I列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 2 columns of
Just [ Num n1, Num n2, Free, Num n4, Num n5 ] -> -- 3番目は FREEマス
[ n1, n2, n4, n5 ]
|> List.all (\n -> 31 <= n && n <= 45)
|> Expect.true "範囲外の値があります。"
Just [ _, _, Num n3, _, _ ] ->
Expect.fail "FREEマスの位置に数字マスが存在します。" -- 3番目に数字マスがあるのは NG
Just [ _, _, _, _, _ ] ->
Expect.fail "数字マスの位置にFREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
これまでと同様にテストコードのテストはなんやかんやで終わらせ、プロダクトコードの実装をしていきます。
3番目のマスを FREEマスにするため、列の値を生成後に 3番目を Free
に置き換える処理を追加します。
List
の任意の要素を置き換えるには List.Extra.setAt
を使用します:
setAt : Int -> a -> List a -> List a
現時点で elm-community/list-extra パッケージはテスト用にインストールしていますが、プロダクトコード用にはインストールしていません (elm-test install
でインストールしたため) 。
なので、改めて elm install
でインストールします:
$ elm install elm-community/list-extra
I found it in your elm.json file, but in the "test-dependencies" field.
Should I move it into "dependencies" for more general use? [Y/n]:
「elm-community/list-extra なら test-dependencies
にあるのを見付けたけど dependencies
に移動させる?」とのこと。
Yes なのでそのままエンターして続行します:
-- INVALID PACKAGE DEPENDENCIES --------------------------------------- elm.json
The dependencies in your elm.json are not compatible.
Did you change them by hand? Try to change it back! It is much better to add
dependencies with elm install or the dependency management tool in elm reactor.
Please ask for help on the Elm slack <http://elmlang.herokuapp.com/> if you try
those paths and still cannot figure it out!
「おめーの elm.json
にある依存パッケージの記述、互換性ねーから。手でいじったやろ? 元に戻して」とのこと。
なんだと。
{
:
"dependencies": {
"direct": {
"elm/browser": "1.0.1",
"elm/core": "1.0.2",
:
},
"indirect": {
:
}
},
"test-dependencies": {
"direct": {
"elm-explorations/test": "1.2.1",
"elm-community/list-extra": "8.2.0"
},
"indirect": {}
}
}
elm
コマンドからすれば elm-tset
コマンドは知らない子なので、そちらのフォーマットをサポートしろと言われても困るんでしょうけど…。
でもパースはできてるのに (test-dependencies
にあるのを見付けているので) 。移動はできないという。えぇ…。
しょうがないので test-dependencies
にある elm-community/list-extra の行を削除し、再度 elm install
を実施しました:
$ elm install elm-community/list-extra
Here is my plan:
Add:
elm-community/list-extra 8.2.0
Would you like me to update your elm.json accordingly? [Y/n]:
Dependencies loaded from local cache.
Dependencies ready!
{
:
"dependencies": {
"direct": {
"elm/browser": "1.0.1",
"elm/core": "1.0.2",
:
"elm-community/list-extra": "8.2.0",
:
},
"indirect": {
:
}
},
"test-dependencies": {
"direct": {
"elm-explorations/test": "1.2.1"
},
"indirect": {}
}
}
テストコードの依存パッケージでも elm install
でインストールする方が無難なのかもしれません。
ともあれこれで List.Extra
モジュールの関数を使えるようになりました。
bingoCardGenerator
の実装を進めていきます:
import List.Extra as ListEx
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45 |> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free) -- 3番目の要素 (添え字は 2) を Free に変更
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
あとは elm-test
を実行します:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 4 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 77513012609946 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 1029 ms
Passed: 4
Failed: 0
よし。
途中経過6
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
, fuzz bingoCardFuzzer "I列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 2 columns of
Just [ Num n1, Num n2, Free, Num n4, Num n5 ] ->
[ n1, n2, n4, n5 ]
|> List.all (\n -> 31 <= n && n <= 45)
|> Expect.true "範囲外の値があります。"
Just [ _, _, Num n3, _, _ ] ->
Expect.fail "FREEマスの位置に数字マスが存在します。"
Just [ _, _, _, _, _ ] ->
Expect.fail "数字マスの位置にFREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import List.Extra as ListEx
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, [ Free, Free, Free, Free, Free ] |> Random.constant
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
G列には 46 以上 60 以下の値が設定される
特に変わったことはないので流し読み推奨。
テストコードを書きます:
verifyColumn "G列には 46 以上 60 以下の値が設定される" 3 46 60
これまでと同じようななんやかんやがあってプロダクトコードも実装します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator -- G列を生成
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
テストします:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 5 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 149363890797378 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 1141 ms
Passed: 5
Failed: 0
OK です。
途中経過7
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
, fuzz bingoCardFuzzer "I列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 2 columns of
Just [ Num n1, Num n2, Free, Num n4, Num n5 ] ->
[ n1, n2, n4, n5 ]
|> List.all (\n -> 31 <= n && n <= 45)
|> Expect.true "範囲外の値があります。"
Just [ _, _, Num n3, _, _ ] ->
Expect.fail "FREEマスの位置に数字マスが存在します。"
Just [ _, _, _, _, _ ] ->
Expect.fail "数字マスの位置にFREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
, verifyColumn "G列には 46 以上 60 以下の値が設定される" 3 46 60
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import List.Extra as ListEx
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, [ Free, Free, Free, Free, Free ] |> Random.constant
]
|> RandomEx.sequence
|> Random.map BingoCard
O列には 61 以上 75 以下の値が設定される
特に変わったことはないので流し読み推奨。
テストコードを書きます:
verifyColumn "O列には 61 以上 75 以下の値が設定される" 4 61 75
これまでと同じようななんやかんやがあってプロダクトコードも実装します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, List.range 61 75 |> toColumnGenerator ]
|> RandomEx.sequence
|> Random.map BingoCard
テストします:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 6 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 343008546702977 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 1436 ms
Passed: 6
Failed: 0
OK です。
途中経過8
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
, fuzz bingoCardFuzzer "I列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 2 columns of
Just [ Num n1, Num n2, Free, Num n4, Num n5 ] ->
[ n1, n2, n4, n5 ]
|> List.all (\n -> 31 <= n && n <= 45)
|> Expect.true "範囲外の値があります。"
Just [ _, _, Num n3, _, _ ] ->
Expect.fail "FREEマスの位置に数字マスが存在します。"
Just [ _, _, _, _, _ ] ->
Expect.fail "数字マスの位置にFREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
, verifyColumn "G列には 46 以上 60 以下の値が設定される" 3 46 60
, verifyColumn "O列には 61 以上 75 以下の値が設定される" 4 61 75
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import List.Extra as ListEx
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, List.range 61 75 |> toColumnGenerator
]
|> RandomEx.sequence
|> Random.map BingoCard
同じ値のマスは 1個のみ存在する
5x5 のマスの値をパターンマッチで取り出し、それらの中で重複がないかを検証します。
テストコードはこうなります:
import List.Extra as ListEx
fuzz bingoCardFuzzer "同じ値のマスは 1個のみ存在する" <|
\(BingoCard columns) ->
case columns of
-- 無慈悲な elm-format によりパターンマッチのリストは 1行にまとめられてしまう。
-- 列ごとに改行したいが諦める。無念。
[ [ Num b1, Num b2, Num b3, Num b4, Num b5 ], [ Num i1, Num i2, Num i3, Num i4, Num i5 ], [ Num n1, Num n2, Free, Num n4, Num n5 ], [ Num g1, Num g2, Num g3, Num g4, Num g5 ], [ Num o1, Num o2, Num o3, Num o4, Num o5 ] ] ->
-- 列ごとに改行するだけだと elm-format により 1行に 1要素の形に展開され、
-- コードが縦長になってしまう。
-- これを防ぐため、列ごとにリストでまとめ (++) で結合するようにした。
-- 無駄な処理だが読みやすさを重視する。
[ b1, b2, b3, b4, b5 ]
++ [ i1, i2, i3, i4, i5 ]
++ [ n1, n2, n4, n5 ]
++ [ g1, g2, g3, g4, g5 ]
++ [ o1, o2, o3, o4, o5 ]
|> ListEx.allDifferent
|> Expect.true "同じ値が複数存在します。"
_ ->
Expect.fail <|
"ビンゴカードのマスの配置が誤っています。\n"
++ "他のテストが失敗しているはずなので、詳細はそちらを確認してください。"
List.Extra.allDifferent
はリスト内に同じ要素が存在しないかを判定する関数です (同じ要素が存在しない場合に True
) :
allDifferent : List comparable -> Bool
なお、パターンマッチ部分は次のように改行しておきたかったのですが、elm-format
がそれを許してくれませんでした。
case columns of
[ [ Num b1, Num b2, Num b3, Num b4, Num b5 ]
, [ Num i1, Num i2, Num i3, Num i4, Num i5 ]
, [ Num n1, Num n2, Free, Num n4, Num n5 ]
, [ Num g1, Num g2, Num g3, Num g4, Num g5 ]
, [ Num o1, Num o2, Num o3, Num o4, Num o5 ]
] ->
:
他の場合は問題ない (改行された状態を保つ) のです、パターンマッチの場合は駄目みたいです。
->
の左側は複数行を許可してないのかもしれません。わからん、全然わからん。
コードの整形についてはひとまず置いておいて、このテストコードもテストします。
たとえば中央に FREEマスがないビンゴカードを生成するよう bingoCardGenerator
を変更します:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
- |> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, List.range 61 75 |> toColumnGenerator
]
|> RandomEx.sequence
|> Random.map BingoCard
この状態で elm-test
を実行すると次のような結果になります:
> N列には 31 以上 45 以下の値が設定される
Given BingoCard [[Num 11,Num 8,Num 7,Num 4,Num 2],[Num 26,Num 16,Num 30,Num 25,Num 24],[Num 32,Num 45,Num 43,Num 42,Num 41],[Num 46,Num 60,Num 58,Num 57,Num 51],[Num 74,Num 73,Num 66,Num 67,Num 72]]
FREEマスの位置に数字マスが存在します。
:
> 同じ値のマスは 1個のみ存在する
Given BingoCard [[Num 11,Num 8,Num 7,Num 4,Num 2],[Num 26,Num 16,Num 30,Num 25,Num 24],[Num 32,Num 45,Num 43,Num 42,Num 41],[Num 46,Num 60,Num 58,Num 57,Num 51],[Num 74,Num 73,Num 66,Num 67,Num 72]]
ビンゴカードのマスの配置が誤っています。
他のテストが失敗しているはずなので、詳細はそちらを確認してください。
ある列のマスが少ない場合はこんな感じです:
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
- , List.range 16 30 |> toColumnGenerator
+ , [ Num 16, Num 17, Num 29, Num 30 ] |> Random.constant
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, List.range 61 75 |> toColumnGenerator
]
|> RandomEx.sequence
|> Random.map BingoCard
> ビンゴカードは 5x5 のマスで構成される
Given BingoCard [[Num 11,Num 8,Num 7,Num 4,Num 2],[Num 16,Num 17,Num 29,Num 30],[Num 41,Num 31,Free,Num 40,Num 39],[Num 47,Num 60,Num 58,Num 57,Num 56],[Num 61,Num 75,Num 73,Num 72,Num
66]]
ビンゴカードが 5x5 のマスで構成されていません。
:
> I列には 16 以上 30 以下の値が設定される
Given BingoCard [[Num 11,Num 8,Num 7,Num 4,Num 2],[Num 16,Num 17,Num 29,Num 30],[Num 41,Num 31,Free,Num 40,Num 39],[Num 47,Num 60,Num 58,Num 57,Num 56],[Num 61,Num 75,Num 73,Num 72,Num
66]]
マスの数が 5 個ではありません。
:
> 同じ値のマスは 1個のみ存在する
Given BingoCard [[Num 11,Num 8,Num 7,Num 4,Num 2],[Num 16,Num 17,Num 29,Num 30],[Num 41,Num 31,Free,Num 40,Num 39],[Num 47,Num 60,Num 58,Num 57,Num 56],[Num 61,Num 75,Num 73,Num 72,Num
66]]
ビンゴカードのマスの配置が誤っています。
他のテストが失敗しているはずなので、詳細はそちらを確認してください。
という感じでテストコードをなんやかんやとテストしていきます。
一通りテストしたらプロダクトコードを元の状態に戻してテストします:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 7 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 230632092059212 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 1448 ms
Passed: 7
Failed: 0
OK です。
途中経過9
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
, fuzz bingoCardFuzzer "N列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 2 columns of
Just [ Num n1, Num n2, Free, Num n4, Num n5 ] ->
[ n1, n2, n4, n5 ]
|> List.all (\n -> 31 <= n && n <= 45)
|> Expect.true "範囲外の値があります。"
Just [ _, _, Num n3, _, _ ] ->
Expect.fail "FREEマスの位置に数字マスが存在します。"
Just [ _, _, _, _, _ ] ->
Expect.fail "数字マスの位置にFREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
, verifyColumn "G列には 46 以上 60 以下の値が設定される" 3 46 60
, verifyColumn "O列には 61 以上 75 以下の値が設定される" 4 61 75
, fuzz bingoCardFuzzer "同じ値のマスは 1個のみ存在する" <|
\(BingoCard columns) ->
case columns of
-- 無慈悲な elm-format によりパターンマッチのリストは 1行にまとめられてしまう。
-- 列ごとに改行したいが諦める。無念。
[ [ Num b1, Num b2, Num b3, Num b4, Num b5 ], [ Num i1, Num i2, Num i3, Num i4, Num i5 ], [ Num n1, Num n2, Free, Num n4, Num n5 ], [ Num g1, Num g2, Num g3, Num g4, Num g5 ], [ Num o1, Num o2, Num o3, Num o4, Num o5 ] ] ->
-- 列ごとに改行するだけだと elm-format により 1行に 1要素の形に展開され、
-- コードが縦長になってしまう。
-- これを防ぐため、列ごとにリストでまとめ (++) で結合するようにした。
-- 無駄な処理だが読みやすさを重視する。
[ b1, b2, b3, b4, b5 ]
++ [ i1, i2, i3, i4, i5 ]
++ [ n1, n2, n4, n5 ]
++ [ g1, g2, g3, g4, g5 ]
++ [ o1, o2, o3, o4, o5 ]
|> ListEx.allDifferent
|> Expect.true "同じ値が複数存在します。"
_ ->
Expect.fail <|
"ビンゴカードのマスの配置が誤っています。\n"
++ "他のテストが失敗しているはずなので、詳細はそちらを確認してください。"
]
]
Main.elm
は変更なしです。module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import List.Extra as ListEx
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, List.range 61 75 |> toColumnGenerator
]
|> RandomEx.sequence
|> Random.map BingoCard
中央のマス (N列の 3段目) 常に FREEマス、それ以外は数字マス
1つ前のテストで 5x5 のマスの値をパターンマッチで取り出して検証 というテストコードにしたので、それをコピペすればほぼ完了です。
値の判定はしないのでそこだけ変更します:
fuzz bingoCardFuzzer "中央のマス (N列の 3段目) 常に FREEマス、それ以外は数字マス" <|
\(BingoCard columns) ->
case columns of
[ [ Num b1, Num b2, Num b3, Num b4, Num b5 ], [ Num i1, Num i2, Num i3, Num i4, Num i5 ], [ Num n1, Num n2, Free, Num n4, Num n5 ], [ Num g1, Num g2, Num g3, Num g4, Num g5 ], [ Num o1, Num o2, Num o3, Num o4, Num o5 ] ] ->
Expect.pass
_ ->
Expect.fail <|
"ビンゴカードのマスの配置が誤っています。\n"
++ "他のテストが失敗しているはずなので、詳細はそちらを確認してください。"
先のテストと同じようになんやかんやの後にプロダクトコードをテストします:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 8 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 152152025322505 elm-bingo-card-tdd\tests\Tests.elm
TEST RUN PASSED
Duration: 1304 ms
Passed: 8
Failed: 0
OK です。
同じ値のマスは 1個のみ存在する のテストと検証内容はほぼ変わらないため「このテストコードは不要では?」と思いましたが、テストコードは 仕様を読み取れること も重要なのでこのコードも残しておくことにしました。
そもそも同じ値のマスの個数を確認するのにパターンマッチは過剰だったかもしれません…。
が、パターンマッチしない場合は値を取り出すのがちょっと面倒になります。
なので今回のテストコードの構成は悪くないものだった、と考えておくことにします。
最終的なコード
module Tests exposing (suite)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import List.Extra as ListEx
import Main exposing (..)
import Random
import Test exposing (..)
bingoCardFuzzer : Fuzzer BingoCard
bingoCardFuzzer =
let
intToBingoCard : Int -> BingoCard
intToBingoCard =
Random.initialSeed >> Random.step bingoCardGenerator >> Tuple.first
in
Fuzz.map intToBingoCard Fuzz.int
verifyColumn : String -> Int -> Int -> Int -> Test
verifyColumn title index min max =
fuzz bingoCardFuzzer title <|
\(BingoCard columns) ->
case ListEx.getAt index columns of
Just [ Num n1, Num n2, Num n3, Num n4, Num n5 ] ->
[ n1, n2, n3, n4, n5 ]
|> List.all (\n -> min <= n && n <= max)
|> Expect.true "範囲外の値があります。"
Just [ _, _, _, _, _ ] ->
Expect.fail "FREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
suite : Test
suite =
describe "Main module"
[ describe "bingoCardGenerator"
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
, fuzz bingoCardFuzzer "N列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
case ListEx.getAt 2 columns of
Just [ Num n1, Num n2, Free, Num n4, Num n5 ] ->
[ n1, n2, n4, n5 ]
|> List.all (\n -> 31 <= n && n <= 45)
|> Expect.true "範囲外の値があります。"
Just [ _, _, Num n3, _, _ ] ->
Expect.fail "FREEマスの位置に数字マスが存在します。"
Just [ _, _, _, _, _ ] ->
Expect.fail "数字マスの位置にFREEマスが存在します。"
Just _ ->
Expect.fail "マスの数が 5 個ではありません。"
Nothing ->
Expect.fail "検証対象の列が存在しません。"
, verifyColumn "G列には 46 以上 60 以下の値が設定される" 3 46 60
, verifyColumn "O列には 61 以上 75 以下の値が設定される" 4 61 75
, fuzz bingoCardFuzzer "同じ値のマスは 1個のみ存在する" <|
\(BingoCard columns) ->
case columns of
-- 無慈悲な elm-format によりパターンマッチのリストは 1行にまとめられてしまう。
-- 列ごとに改行したいが諦める。無念。
[ [ Num b1, Num b2, Num b3, Num b4, Num b5 ], [ Num i1, Num i2, Num i3, Num i4, Num i5 ], [ Num n1, Num n2, Free, Num n4, Num n5 ], [ Num g1, Num g2, Num g3, Num g4, Num g5 ], [ Num o1, Num o2, Num o3, Num o4, Num o5 ] ] ->
-- 列ごとに改行するだけだと elm-format により 1行に 1要素の形に展開され、
-- コードが縦長になってしまう。
-- これを防ぐため、列ごとにリストでまとめ (++) で結合するようにした。
-- 無駄な処理だが読みやすさを重視する。
[ b1, b2, b3, b4, b5 ]
++ [ i1, i2, i3, i4, i5 ]
++ [ n1, n2, n4, n5 ]
++ [ g1, g2, g3, g4, g5 ]
++ [ o1, o2, o3, o4, o5 ]
|> ListEx.allDifferent
|> Expect.true "同じ値が複数存在します。"
_ ->
Expect.fail <|
"ビンゴカードのマスの配置が誤っています。\n"
++ "他のテストが失敗しているはずなので、詳細はそちらを確認してください。"
, fuzz bingoCardFuzzer "中央のマス (N列の 3段目) 常に FREEマス、それ以外は数字マス" <|
\(BingoCard columns) ->
case columns of
[ [ Num b1, Num b2, Num b3, Num b4, Num b5 ], [ Num i1, Num i2, Num i3, Num i4, Num i5 ], [ Num n1, Num n2, Free, Num n4, Num n5 ], [ Num g1, Num g2, Num g3, Num g4, Num g5 ], [ Num o1, Num o2, Num o3, Num o4, Num o5 ] ] ->
Expect.pass
_ ->
Expect.fail <|
"ビンゴカードのマスの配置が誤っています。\n"
++ "他のテストが失敗しているはずなので、詳細はそちらを確認してください。"
]
]
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
import List.Extra as ListEx
import Random exposing (Generator)
import Random.Extra as RandomEx
import Random.List as RandomList
type Cell
= Num Int
| Free
type BingoCard
= BingoCard (List (List Cell))
bingoCardGenerator : Generator BingoCard
bingoCardGenerator =
let
toColumnGenerator : List Int -> Generator (List Cell)
toColumnGenerator ints =
ints
|> List.map Num
|> RandomList.shuffle
|> Random.map (List.take 5)
in
[ List.range 1 15 |> toColumnGenerator
, List.range 16 30 |> toColumnGenerator
, List.range 31 45
|> toColumnGenerator
|> Random.map (ListEx.setAt 2 Free)
, List.range 46 60 |> toColumnGenerator
, List.range 61 75 |> toColumnGenerator
]
|> RandomEx.sequence
|> Random.map BingoCard
完成
テストが全て OK だったので bingoCardGenerator
は仕様通りの実装ができていると思っていいでしょう。
これで完成です。
あとはビンゴカードを表示する部分やビンゴカード生成の起点となるイベントを管理する部分が必要ですね。
その内やります。やるんじゃないかな。やるといいな。
fuzz
の補足
「fuzz
で 100パターンのデータが用意されるってのはホンマなんかワレ?」という疑問があるかもしれません。
テストを全部やっても結果が Passed: 8
とかですし、実際には 1パターンかもしれませんよね。
というわけで print デバッグで確認しましょう。
Elm で print デバッグする方法は @miyamo_madoka さんによる Elm初心者でもできるprintデバッグ が参考になります。
今回作成したテストの内、2件に対して次のように行を追加します:
[ fuzz bingoCardFuzzer "ビンゴカードは 5x5 のマスで構成される" <|
\(BingoCard columns) ->
let
+ _ = Debug.log "test1-columns" columns
hasOnlyFiveElements : List a -> Bool
hasOnlyFiveElements aList =
List.length aList == 5
in
Expect.true "ビンゴカードが 5x5 のマスで構成されていません。" <|
hasOnlyFiveElements columns
&& List.all hasOnlyFiveElements columns
, verifyColumn "B列には 1 以上 15 以下の値が設定される" 0 1 15
, verifyColumn "I列には 16 以上 30 以下の値が設定される" 1 16 30
, fuzz bingoCardFuzzer "N列には 31 以上 45 以下の値が設定される" <|
\(BingoCard columns) ->
+ let
+ _ = Debug.log "test2-columns" columns
+ in
case ListEx.getAt 2 columns of
:
この状態でテストを実行します:
$ elm-test
elm-test 0.19.0-rev6
--------------------
Running 8 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 256308025512112 elm-bingo-card-tdd\tests\Tests.elm
test1-columns: [[Num 13,Num 12,Num 11,Num 10,Num 2],[Num 22,Num 21,Num 28,Num 27,Num 25],[Num 40,Num 38,Free,Num 41,Num 37],[Num 47,Num 50,Num 55,Num 51,Num 46],[Num 75,Num 74,Num 73,Num 70,Num 71]]
test2-columns: [[Num 5,Num 4,Num 15,Num 11,Num 6],[Num 29,Num 30,Num 28,Num 27,Num 26],[Num 37,Num 36,Free,Num 41,Num 31],[Num 46,Num 59,Num 58,Num 55,Num 50],[Num 71,Num 62,Num 74,Num 65,Num 61]]
test1-columns: [[Num 8,Num 13,Num 4,Num 7,Num 2],[Num 24,Num 22,Num 26,Num 16,Num 21],[Num 40,Num 42,Free,Num 32,Num 35],[Num 53,Num 52,Num 51,Num 50,Num 49],[Num 62,Num 61,Num 75,Num 74,Num 69]]
test1-columns: [[Num 8,Num 4,Num 10,Num 3,Num 7],[Num 18,Num 17,Num 30,Num 29,Num 21],[Num 45,Num 44,Free,Num 42,Num 41],[Num 60,Num 59,Num 57,Num 46,Num 56],[Num 62,Num 75,Num 73,Num 71,Num 74]]
test2-columns: [[Num 11,Num 8,Num 7,Num 4,Num 2],[Num 26,Num 16,Num 30,Num 25,Num 24],[Num 32,Num 45,Free,Num 42,Num 41],[Num 46,Num 60,Num 58,Num 57,Num 51],[Num 74,Num 73,Num 66,Num 67,Num 72]]
test2-columns: [[Num 7,Num 5,Num 3,Num 2,Num 10],[Num 22,Num 21,Num 29,Num 30,Num 20],[Num 31,Num 39,Free,Num 42,Num 35],[Num 59,Num 57,Num 55,Num 54,Num 53],[Num 63,Num 62,Num 61,Num 68,Num 67]]
test1-columns: [[Num 6,Num 4,Num 2,Num 9,Num 5],[Num 21,Num 20,Num 25,Num 19,Num 18],[Num 36,Num 34,Free,Num 43,Num 41],[Num 55,Num 53,Num 54,Num 52,Num 56],[Num 62,Num 61,Num 75,Num 72,Num 71]]
test1-columns: [[Num 6,Num 5,Num 4,Num 3,Num 2],[Num 27,Num 24,Num 23,Num 19,Num 22],[Num 39,Num 45,Free,Num 44,Num 43],[Num 48,Num 47,Num 49,Num 57,Num 56],[Num 66,Num 65,Num 62,Num 61,Num 74]]
test2-columns: [[Num 6,Num 5,Num 4,Num 13,Num 15],[Num 24,Num 16,Num 21,Num 30,Num 22],[Num 40,Num 45,Free,Num 38,Num 32],[Num 48,Num 55,Num 49,Num 50,Num 47],[Num 68,Num 67,Num 64,Num 75,Num 71]]
:
TEST RUN PASSED
Duration: 2383 ms
Passed: 8
Failed: 0
長いので割愛しましたが、test1-columns
の行と test2-columns
の行がそれぞれ 100行ずつ出力されました。
実際にテスト 1件につき 100パターンのビンゴカードが作成されているようです。
また、出力をよく見ると test1-columns
の行と test2-columns
の行が不規則に出現しているのが分かります。
elm-test
は各テストを 1件ずつ実行するのではなく、複数のテストを同時に実行しているようです。
Elm の世界には副作用が存在しないので 1件ずつ実行する必要性がないということでしょう。なるほどなー。
おまけ
elm-explorations/test パッケージ の文を引用して終わります。
Not even your test modules can import unexposed functions, so test them only as the exposed interface uses them. Don't expose a function just to test it. Every exposed function should have tests. (If you practice TDD, this happens automatically!)
テストするためだけに関数を公開しないでください。
module Main exposing (BingoCard(..), Cell(..), bingoCardGenerator)
ああ…。
Avoid importing your
Main
module. Most of your code belongs in other modules, so import those instead.
Main
モジュールをインポートしないでください。
import Main exposing (..)
あああ…。
許して…許して…。
おしまり
続き
-
elm-analyze の説明では "Using a record is obsolete if you only plan to store a single field in it." とあるので、将来的にフィールドが複数になるのであれば問題視するつもりはない模様。Elmガイドの内容とも矛盾しない。でもちょっと引っかかる…。 ↩
-
基礎からわかるElm の著者。 ↩
-
100 はデフォルト値。この値は
elm-test
のオプション--fuzz
で指定可能。 ↩ -
なぜか同じ機能の
combine
という関数もRandom.Extra
モジュールに存在する 。追加されたのはcombine
の方が先。 ↩