haskell で behavior driven developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 haskell で...

56
1 Haskell Behavior Driven Development 2012.5.27 @kazu_yamamoto

Upload: others

Post on 13-Jul-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

1

Haskell でBehavior Driven Development

2012.5.27

山本和彦@kazu_yamamoto

Page 2: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

2

自己紹介

山本和彦二児の父

Haskell 歴は4年と少しMew、Firemacs、Mighty、ghc-mod の開発者

Page 3: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

3

Ruby/Java ユーザのつぶやき HackageDB のあるライブラリを見て

Page 4: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

4

Haskeller はテストコードをあんまり書かない!

Page 5: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

5

今日のお題

Haskeller のみなさんもっとテストコードを書きましょう

Page 6: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

6

Q) なぜ Haskeller はあまりテストコードを書かないのか?

A) コンパイルが通ればだいたい思い通りに動くから

Page 7: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

7

なぜならコンパイルに通れば

引数の数に関する間違いとか

関数名のタイポとか絶対にない

Page 8: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

8

他の静的型付け言語のユーザ曰く

そんなの当たり前じゃない?

Haskeller 曰く

実はもっとすごいHaskell は型安全なんです

Page 9: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

9

型安全?何それ?

Page 10: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

10

役に立つ静的型付け

Page 11: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

11

Haskell ではすべてが式!

Page 12: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

12

Haskell のコンパイルはテスト 文と文の型の関係は検査されない 式と式の型の関係は検査される Haskell ではあらゆる場所が検査される

Page 13: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

13

しかも

Page 14: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

14

Haskell には型を台無しするものがない

Page 15: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

15

つまり

Haskell では、コンパイルが通れば型に関する間違いがない

Page 16: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

16

大切なことなので二度言いますが

Haskell はコンパイルが通れば

だいたい思い通りに動く

Page 17: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

17

型安全ばんざーい \^^/

Page 18: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

18

が、しかし

Page 19: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

19

型に関する間違いがないとしても値に関する間違いは存在する

Page 20: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

20

Haskeller 曰く

HUnit のテストコードを書くのは面倒だからQuickCheck に

コーナーケースを見つけてもらえばいいんじゃない?

Page 21: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

21

QuickCheck って何? 関数の性質を記述する prop_doubleSort :: [Int] -> Bool prop_doubleSort xs = sort xs == sort (sort xs)

テストケースを乱数で生成してくれる > quickCheck prop_doubleSort +++ OK, passed 100 tests.

Page 22: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

22

純粋な関数は性質を見つけやすい

自分が書いたコードの性質は分かっているはず訓練すればそれを表現できる

Page 23: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

23

Haskell では型で純粋か分かる 純粋な関数 QuickCheck がおススメ sort :: Ord a => [a] -> [a] lookup :: Eq a => a -> [(a, b)] -> Maybe b delete :: Eq a => a -> [a] -> [a]

副作用があるかもしれない関数 HUnit がおススメ(だった)

hGetLine :: Handle -> IO String writeFile :: FilePath -> String -> IO () forkIO :: IO () -> IO ThreadId

Page 24: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

24

「ビューティフルコード」7章 ビューティフル・テスト

二分探索に対するテスト

二分探索のアイディアは1946年に出された

バグがない実装ができたのは12年後

Page 25: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

25

美しきテストたち 線形探索を使いながら二分探索をテストする

Page 26: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

26

でも、それって

二分探索が線形探索と同じって言ってるだけじゃん!

Page 27: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

27

QuickCheck すごい QuickCheck だと、仕様はモデル実装と同じ と表現するだけ!

prop_model x xs = linearSearch x xs == binarySearch x xs

Page 28: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

28

QuickCheck がすごいとして

Page 29: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

29

Haskeller にはテストを書きたくなる仕組みが必要

Page 30: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

30

Simon HENGEL さんは言った

「doctest あれ」

Page 31: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

31

doctest って何? Python のドキュメントに利用例を書く仕組み def factorial(n): """Return the factorial of n, an exact integer >= 0. If the result is small enough to fit in an int, return an int. Else return a long.

>>> [factorial(n) for n in range(6)] [1, 1, 2, 6, 24, 120] >>> factorial(30) 265252859812191058636308480000000L """ import math if not n >= 0: raise ValueError("n must be >= 0") ...

利用例を自動的にテストできる

Page 32: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

32

Haskell のドキュメント・ツールは

Haddock

Page 33: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

33

Haddock コメントの中にドキュメントを書く 各種マークアップが定義されている

-- | ’unlines’ is an inverse operation to ’lines’. -- It joins lines, after appending a terminating -- newline to each.

unlines :: [String] -> String unlines [] = [] unlines (l:ls) = l ++ ’\n’ : unlines ls

Haddock が生成する HTML

Page 34: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

34

コードブロックは使えるか? コードブロック用のマークアプは ">" Data.Map より -- | /O(1)/. Is the map empty? -- -- > Data.Map.null (empty) == True -- > Data.Map.null (singleton 1 ’a’) == False

null :: Map k a -> Bool null Tip = True null (Bin _ _ _ _ _) = False

利用例だと思うと、 どこが式でどこが結果が分からない 性質だと思うと、利用例とは区別したい

Page 35: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

35

マークアップに関する結論 利用例用のマークアップが必要 性質用のマークアップが必要

Page 36: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

36

利用例用のマークアップ Simon HENGEL さんが ">>>" を導入 式と結果で利用例を記述する >>> length [] 0

命令シーケンスも書ける >>> writeFile "tmpfile" "Hello" >>> readFile "tmpfile" "Hello"

例外も書ける >>> head [] *** Exception: Prelude.head: empty list

Haddock 2.8 以降で利用可能

Page 37: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

37

doctest の実装 Haddock コメントから利用例を切り出す 0.5.2 以前は Haddock API を利用 0.6 以降は GHC API を利用

GHCi で評価して、文字列で結果を比較 doctest コマンドとライブラリ関数がある

0.6.x 以前は、関数ごとに GHCi を起動していた 0.7 以降は、モジュールごとに GHCi 起動する 爆速です きりっ

Page 38: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

38

性質用のマークアップ 山本和彦が "prop>" を導入 パラメータのない性質 prop> Data.Map.null empty == True

パラメータのある性質は無名関数で 型は、型シグニチャで補う prop> \xs -> sort xs == sort (sort (xs::[Int]))

無名関数のプレフィックスは省略したい prop> sort xs == sort (sort (xs::[Int]))

将来の Haddock で利用可能になる予定

Page 39: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

39

どうやってプレフィックスを補うか? haskell-src-exts でもパースできるけど...

GHCi が教えてくれる! > sort xs == sort (sort (xs::[Int])) <interactive>:1:6: Not in scope: ‘xs’ <interactive>:1:24: Not in scope: ‘xs’

この例では "\xs -> " を補えばよい

Page 40: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

40

という訳で将来の doctest では

利用例と性質が扱えます

名付けてdoctest による

設計、ドキュメント、自動テストの三位一体化あるいは

一粒で三度お美味しい

Page 41: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

41

QuickCheck の余談 v2.3 以前 > quickCheck $ length [] == 0 +++ OK, passed 100 tests.

結果はメモ化されるので、True との比較が 100 回

v2.4? 以降 > quickCheck $ length [] == 0 +++ OK, passed 1 tests.

Page 42: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

42

ところでドキュメントにふさわしくない利用例/性質はどうするんですか?

たとえばチケットが切られたコーナーケース

> valid $ deleteMin $ deleteMin $ fromList [(i,())|i <- [0,2,5,1,6,4,8,9,7,11,10,3]] False

Page 43: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

43

Trystan SPANGLER さんは言った

「Hspec あれ」

Page 44: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

44

Q) Hspec って何?

A) Ruby の Rspec の Haskell 版Rspec は BDD の旗手

Page 45: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

45

Q) BDD (Behavior Driven Development)って何?

A) TDD (Test Driven Development)のテストコードを設計の言葉で書くこと

仕様書が自動テストとして使える

Page 46: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

46

注意

Haskell のコードは静的なので「振る舞い」という言葉は不適切かも

Page 47: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

47

Hspec の例 先ほどのコーナーケース describe "deleteMin" $ do it "maintains the balance even if applied doubly" $ valid $ deleteMin $ deleteMin $ fromList [(i::Int,())|i <- [0,2,5,1,6,4,8,9,7,11,10,3]]

it ...

it ...

it ...

Page 48: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

48

IO の setup と teardown それ高階関数でできるよ! withDB action = bracket (connectSqlite3 "my.db") disconnect action

describe "database adaptor" $ do it "returns 23, when 23 is selected" $ withDB $ \connection -> run connection "select 23;" ‘shouldReturn‘ 23

Page 49: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

49

材料が揃ったので Haskell でのBehavior Driven Development

を考えてみる

Page 50: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

50

Haskell で BDD (1) (1) シグニチャと関数名を書く rev :: [a] -> [a] rev = undefined

関数定義は undefined で undefined では、実行時にどこで落ちたのか分からない undefined の代わりに placeholders ライブラリの notImplemented と todo もおススメ 型のレベルで設計すること そうすれば型が実装を導いてくれる

Page 51: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

51

Haskell で BDD (2) (2) Haddock スタイルでユーザ用の仕様を書く -- | -- ’rev’ @xs@ returns the elements of @xs@ -- in reverse order. @xs@ must be finite. -- -- >>> rev [1,2,3] -- [3,2,1] -- -- prop> rev [] == rev [] -- prop> rev (xs++ys) == rev ys ++ rev (xs::[Int]) -- prop> rev (rev xs) == (xs::[Int])

rev :: [a] -> [a] rev = undefined

実行して文法間違いがないか確かめる

Page 52: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

52

Haskell で BDD (3) (3) Hspec スタイルで開発者用の仕様を書く describe "rev" $ do it "returns the first element in the last" $ property $ \xs -> not (null xs) ==> head (rev xs) == last (xs :: [Int])

it "returns the last element in the first" $ property $ \xs -> not (null xs) ==> last (rev xs) == head (xs :: [Int])

実行して文法間違いがないか確かめる

Page 53: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

53

Haskell で BDD (4) (4) 実装する rev :: [a] -> [a] rev = foldl (flip (:)) []

doctest と Hspec のテストがすべて 通るまで修正を繰り返す Cabal で自動化しておくのがおススメ cabal test

test-suite doctests type: exitcode-stdio-1.0 main-is: doctests.hs build-depends: base, doctest

test-suite spec type: exitcode-stdio-1.0 main-is: Spec.hs build-depends: base, hspec

Page 54: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

54

免責 僕は原理主義者ではありません 実装が簡単なら、まず実装して rev = foldl (flip (:)) []

ghc-mod などで推測したシグニチャを挿入し rev :: [a] -> [a] rev = foldl (flip (:)) []

後から例と性質を書いてもいいでしょう -- | -- >>> rev [1,2,3] -- [3,2,1] rev :: [a] -> [a] rev = foldl (flip (:)) []

Page 55: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

55

まとめ

QuickCheck を統合したdoctest と Hspec で

仕様(テストコード)を書きCabal でまとめて自動テストしよう!

一粒で三度おいしいよ!

Page 56: Haskell で Behavior Driven Developmentkazu/material/2012-bdd.pdf · 2014-06-17 · 1 Haskell で Behavior Driven Development 2012.5.27 山本和彦 @kazu_yamamoto

56

おまけ

Simon HENGEL さんからのお願い(doctest の作者、Hspec の貢献者)

Hspec に足りない機能があったらgithub の issue に登録してね