テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(msgpack-nim report) HaskellのQuickCheckでmsgpack-nimをテストする

QuickCheckは, テストケースを網羅的に自動生成するためのHaskellライブラリである.

msgpackバインディングのテストにはこのライブラリを使うのがとても有利だと思ったので, 使うことにした. しかし, nim上で定義した性質とQuickCheckを直接合体させることは出来ない. そこで, 一度テストケースをファイルに出力して, それをnimにごっくんさせることにした.

以下のコードはいい加減なHaskellコードだし, そもそも完成していないが, Msg型のリストを吐く. Showインスタンスを自前で定義して, nimコードに埋め込めるようにするのがいい. この後は, msgpack-nimのコードに倣ってMsg型を拡張していくのと, showをそれぞれ実装することだけの簡単なお仕事である.

全部手作業とか人海戦術とか, 自分が参加してなくても聞くだけで嫌になるよね. すべきところで自動化しないと大損害を負う. マニーがなくなる.

import Test.QuickCheck
import Control.Monad

data Msg =
    MsgNil
  | MsgFalse
  | MsgTrue 
  | MsgFixArray [Msg]
  deriving (Show)

-- instance Show Msg where
--   show x = "hoge"

instance Arbitrary Msg where
  arbitrary = do
    n <- choose (1,4) :: Gen Int
    case n of
      1 -> return MsgNil
      2 -> return MsgFalse
      3 -> return MsgTrue
      4 -> do
        l <- choose (1,3) :: Gen Int
        list <- sequence $ [arbitrary :: Gen Msg | _ <- [1..l]]
        return $ MsgFixArray list

main = do
  msges <- sequence $ [generate (arbitrary :: Gen Msg) | _ <- [1..10]] :: IO [Msg]
  forM_ msges (\msg -> print $ msg)

(追記)

はい終了(略して拝承). 以下のように実装して, あとはrubyスクリプトをちょこちょこっと書くと,

msgShow MsgNil = "Nil()"
msgShow MsgFalse = "False()"
msgShow MsgTrue = "True()"
msgShow (MsgFixArray xs) = "FixArray(@[" ++ (intercalate "," $ map msgShow xs) ++ "])"

instance Show Msg where
   show = msgShow

こんな感じで1000ケースほどテストをさくっと自動生成してくれて, あとはこれが永遠にTravisで回り続ける. 感動. プログラミングの力で人海戦術軍団に勝った.

when isMainModule:
  t(Nil())
  t(False())
  t(False())
  t(False())
  t(True())
  t(FixArray(@[Nil(),False(),FixArray(@[False(),False()])]))
  t(False())
  t(FixArray(@[True(),Nil(),False()]))
  t(False())
  t(False())
  t(False())
  t(True())
  t(False())
  t(Nil())
  t(Nil())
  t(FixArray(@[True()]))
  t(False())
  t(False())
  t(Nil())
  t(FixArray(@[False()]))
  t(FixArray(@[False(),True(),False()]))
  ...

(追記)

増やしていくうちに整理して, 以下のような形になった. Haskellは美しいですね.

instance Arbitrary Msg where 
  arbitrary = do
    frequency [
        (vw, return MsgNil)
      , (vw, return MsgFalse)
      , (vw, return MsgTrue)
      , (aw, liftM MsgFixArray $ choose (1, 7) >>= randMsg)
      , (aw, liftM MsgArray16 $ choose (1, 10) >>= randMsg)
      , (aw, liftM MsgArray32 $ choose (1, 10) >>= randMsg)
      , (mw, liftM MsgFixMap $ choose (1, 5) >>= randMap)
      , (mw, liftM MsgMap16 $ choose (1, 10) >>= randMap)
      , (mw, liftM MsgMap32 $ choose (1, 10) >>= randMap)
      , (vw, liftM MsgPFixNum $ choose (0, maxUS 7))
      , (vw, liftM MsgNFixNum $ choose (0, maxUS 5))
      , (vw, liftM MsgU8 $ choose (0, maxUS 8))
      , (vw, liftM MsgU16 $ choose (0, maxUS 16))
      , (vw, liftM MsgU32 $ choose (0, maxUS 32))
      , (vw, liftM MsgU64 $ choose (0, maxUS 63))
      , (vw, liftM MsgFixStr $ choose (0, 31) >>= randStr)
      , (vw, liftM MsgStr8 $ choose (0, 31) >>= randStr)
      , (vw, liftM MsgStr16 $ choose (0, 31) >>= randStr)
      , (vw, liftM MsgStr32 $ choose (0, 31) >>= randStr)
      , (vw, liftM MsgFloat32 $ arbitrary)
      , (vw, liftM MsgFloat64 $ arbitrary)
      , (vw, liftM MsgBin8 $ choose (0, 10) >>= randBinSeq)
      , (vw, liftM MsgBin16 $ choose (0, 10) >>= randBinSeq)
      , (vw, liftM MsgBin32 $ choose (0, 10) >>= randBinSeq)
      ]