QuickCheckも動かす:test-framework

Haskellを使う段階においては、HUnitよりもQuickCheckが必要なんじゃないかと思います。だから本当はQuickCheck用のテストと少しのHUnitテストを書くことになると思うのですが、これを同時に扱うライブラリにtest-frameworkがあります。

まず挿入ソートを前の記事のようにHUnitを使って書いてみます。出来上がったのは次のようなものです。
[test_sort.hs]

module SortTests where
import MySort
import Test.HUnit

test_insert1, test_insert2, test_isort1, test_isort2,test_isort3, test_isort4 :: Test
test_insert1 = TestCase(assertEqual "" (insert 1 []) [1])
test_insert2 = TestCase(assertEqual "" (insert 2 [1,3,5]) [1,2,3,5])
test_isort1 = TestCase(assertEqual "" (isort []) [])
test_isort2 = TestCase(assertEqual "" (isort [1]) [1])
test_isort3 = TestCase(assertEqual "" (isort [2,1]) [1,2])
test_isort4 = TestCase(assertEqual "" (isort [3,2,1,4]) [1,2,3,4])
tests :: Test
tests = TestList [
          TestLabel "test_insert1" test_insert1,
          TestLabel "test_insert2" test_insert2,
          TestLabel "test_isort1" test_isort1,
          TestLabel "test_isort2" test_isort2,
          TestLabel "test_isort3" test_isort3,
          TestLabel "test_isort4" test_isort4
        ]
main = runTestTT tests

[sort.hs]

module MySort where
insert :: Int -> [Int] -> [Int]
insert x [] = [x]
insert x yys@(y:ys) | x < y = x:yys
                    | otherwise = y: insert x ys
isort :: [Int] -> [Int]
isort [] = []
isort (x:xs) = insert x (isort xs)

うーん、これだけではこれで良いのか不安です。それで、QuickCheckを使って複数のインプットを使ったテストをします。QuickCheckはQuickCheckで書くこともできますが、一緒に扱えるtest-frameworkの方がおそらく便利でしょう。テストをtest-framework用に書き換えてみましょう。

module SortTests where
import MySort
import Test.Framework (defaultMain, testGroup)
import Test.Framework.Providers.HUnit
import Test.HUnit
main = defaultMain tests
tests = [testGroup "HUnitTests" [
          testCase "test_insert1" test_insert1,
          testCase "test_insert2" test_insert2,
          testCase "test_isort1" test_isort1,
          testCase "test_isort2" test_isort2,
          testCase "test_isort3" test_isort3,
          testCase "test_isort4" test_isort4
          ]
        ]
test_insert1 = assertEqual "" (insert 1 []) [1]
test_insert2 = assertEqual "" (insert 2 [1,3,5]) [1,2,3,5]
test_isort1 = assertEqual "" (isort []) []
test_isort2 = assertEqual "" (isort [1]) [1]
test_isort3 = assertEqual "" (isort [2,1]) [1,2]
test_isort4 = assertEqual "" (isort [3,2,1,4]) [1,2,3,4]

ではリロードしてテストを実行してみます。


[star]SortTests> main
Loading package syb ... linking ... done.
Loading package base-3.0.3.0 ... linking ... done.
Loading package array-0.2.0.0 ... linking ... done.
Loading package containers-0.2.0.0 ... linking ... done.
Loading package bytestring-0.9.1.4 ... linking ... done.
Loading package old-locale-1.0.0.1 ... linking ... done.
Loading package old-time-1.0.0.1 ... linking ... done.
Loading package random-1.0.0.1 ... linking ... done.
Loading package regex-base-0.72.0.2 ... linking ... done.
Loading package regex-posix-0.72.0.3 ... linking ... done.
Loading package time-1.1.2.2 ... linking ... done.
Loading package unix-2.3.1.0 ... linking ... done.
Loading package ansi-terminal-0.5.5 ... linking ... done.
Loading package ansi-wl-pprint-0.5.1 ... linking ... done.
Loading package extensible-exceptions-0.1.1.2 ... linking ... done.
Loading package hostname-1.0 ... linking ... done.
Loading package xml-1.3.7 ... linking ... done.
Loading package test-framework-0.3.3 ... linking ... done.
Loading package test-framework-hunit-0.2.6 ... linking ... done.
HUnitTests:
test_insert1: [OK]
test_insert2: [OK]
test_isort1: [OK]
test_isort2: [OK]
test_isort3: [OK]
test_isort4: [OK]

Test Cases Total
Passed 6 6
Failed 0 0
Total 6 6
[star3] Exception: ExitSuccess


QuickCheckのテストを追加します。

module SortTests where
import MySort
import Test.Framework (defaultMainWithArgs, testGroup)
..
import Test.Framework.Providers.QuickCheck (testProperty)
import Test.QuickCheck
main = defaultMainWithArgs tests
..
          testCase "test_isort4" test_isort4,
          testCase "test_is_sorted1" test_is_sorted1,
          testCase "test_is_sorted2" test_is_sorted2,
          testCase "test_is_sorted3" test_is_sorted3,
          testCase "test_is_sorted4" test_is_sorted4
          ],
         testGroup "QuickTests" [
          testProperty "isort" prop_isort1
         ]
        ]
..
test_is_sorted1 = assertBool "" (isSorted [])
test_is_sorted2 = assertBool "" (isSorted [4])
test_is_sorted3 = assertBool "" (isSorted [1,2,3,4])
test_is_sorted4 = assertBool "" (not (isSorted [3,2,1,4]))
prop_isort1 xs = isSorted(isort xs) == True
  where types = (xs :: [Int]) 

これでテスト側は準備OKです。sort.hsにisSortedを実装したとして、テストを実行します。


SortTests> main ["-a 5000", "-j 2"]
HUnitTests:
test_insert1: [OK]
test_insert2: [OK]
test_isort1: [OK]
test_isort2: [OK]
test_isort3: [OK]
test_isort4: [OK]
test_is_sorted1: [OK]
test_is_sorted2: [OK]
test_is_sorted3: [OK]
test_is_sorted4: [OK]
QuickTests:
isort: [OK, passed 5000 tests]

Properties Test Cases Total
Passed 1 10 11
Failed 0 0 0
Total 1 10 11

Exception: ExitSuccess


"-a"でQuickChcekの実行回数を指定できます。"-j"はスレッド数の指定ができます。その他のオプションについてはmain ["--help"]などで見てください。

関連記事