[Elm] 너만의 최강 Fuzzer를 손에 넣어라!

세상에는 property based testing 이라고 하는 테스트 기법이 있습니다.
테스트 대상의 함수가 만족해야 하는 성질을 기술하면(자), 그 성질을 조사하기에 적절한 입력 데이터를 자동 생성해 실제로 함수에 맞추어, 그 출력이 예상하고 있는 성질을 채우고 있는지를 체크하는 테스트 기법 입니다.
Elm에서는 elm-test의 Fuzz 모듈을 사용하는 것이 일반적입니다.

Fuzz 모듈의 기본 사용법 자체는 @jinjor 님이 이미 기사를했습니다..
이 기사에서는 Fuzzer의 합성 및 변환에 대해 자세히 파악하여 세계에 하나만 눈에 넣어도 아프지 않는 귀여운 우리 아이와 같은 Fuzzer를 얻을 수있는 방법을 소개합니다.
여기에서 내 귀여운 사랑하는 딸 사쿠라 짱을보십시오.



이후의 내용은, elm-test 4.20 (Elm 0.18) 를 상정하고 있습니다.

레벨 0. 프리미티브 녀석



오리지널 Fuzzer 라고 말할 때, 갑자기 전혀 오리지널이 아닌 녀석입니다.
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를 얻을 수 있습니다.
이런 녀석입니다.
intRange : Int -> Int -> Fuzzer Int
floatRange : Float -> Float -> Fuzzer Float
constant : a -> Fuzzer a

다음과 같이 원본Fuzzer을 만들 수 있습니다.
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."



레벨 2. 간단한 합성



레벨 0과 레벨 1의 Fuzzer에서 Maybe , Result , List와 같은 유형의 Fuzzer를 만들어 봅시다.
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)
{-| 以下のいずれかが生成される.
* `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. 합성 방법을 직접 지정


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
-- 自分で作った型の 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은 BNF 표기법에 표시된 사양에 따라 문자열을 생성합니다.
그 결과를 다시 문자열로 인코딩한 것과 원래의 문자열이 일치하는지 확인하는 것으로 정상계의 테스트로 하고 있습니다.
BNF로 작성할 필요가 없을 정도로 단순한 것이라면 레벨 3의 String.filter를 사용한 것으로 충분합니다만, Regex 녀석이라고 생각합니다.
-- ListであたえたいずれかのFuzzerを使う
oneOf : List (Fuzzer a) -> Fuzzer a
-- `oneOf` とほぼ同じだけど、Listの要素の各Fuzzerの重要度を指定すると、
-- 重要度が高いFuzzerを優先的に使ってくれる。
frequency : List (Float, Fuzzer a) -> Fuzzer a

겨우, RFC6838의 restricted-name 을 생성하는 Fuzzer 를 만들어 보겠습니다.
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를 얻으십시오!


사쿠라 짱에게 밥을 준다.

좋은 웹페이지 즐겨찾기