Yesodのテストを読み解く

Yesodは単体テストのための仕組みも備えています。

stackのテンプレートから作ったプロジェクトでは、　``` test ``` ディレクトリの下にテスト用のファイルが格納されています。

今回はテスト関連のファイルを読み解いていきましょう。

```
▾ test/
  ▾ Handler/
      CommentSpec.hs
      CommonSpec.hs
      HomeSpec.hs
      ProfileSpec.hs
    Spec.hs
    TestImport.hs
```

# Spec.hs

このファイルの中身はコメントだけです。

```Spec.hs
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
```

このコメントは、HaskellのコンパイラであるGHCのオプションを指定していてます。
テストにおいては ``` hspec-discover ``` が大事で、これはテストが書かれたファイルを自動で探してくれるようにする指令です。

# TestImport.hs

このファイルには、テストの中で使う関数をまとめてエクスポートしてくれています。
``` Model ``` モジュールや ``` Foundation ``` のようなアプリケーションコードに加え、``` Yesod.Test ``` のようなテスト専用のモジュールをエクスポートしています。

import部分までを抜粋します（Sqliteの場合）。

```TestImport.hs
module TestImport
    ( module TestImport
    , module X
    ) where

import Application           (makeFoundation, makeLogWare)
#if MIN_VERSION_classy_prelude(1, 0, 0)
import ClassyPrelude         as X hiding (delete, deleteBy, Handler)
#else
import ClassyPrelude         as X hiding (delete, deleteBy)
#endif
import Database.Persist      as X hiding (get)
import Database.Persist.Sql  (SqlPersistM, SqlBackend, runSqlPersistMPool, rawExecute, rawSql, unSingle, connEscapeName)
import Foundation            as X
import Model                 as X
import Test.Hspec            as X
import Yesod.Default.Config2 (useEnv, loadYamlSettings)
import Yesod.Auth            as X
import Yesod.Test            as X

-- Wiping the database
import Database.Persist.Sqlite              (sqlDatabase, wrapConnection, createSqlPool)
import qualified Database.Sqlite as Sqlite
import Control.Monad.Logger                 (runLoggingT)
import Settings (appDatabaseConf)
import Yesod.Core (messageLoggerSource)
```

# テスト本体

``` test/Handler ``` ディレクトリの中に置かれたファイルが、テスト本体です。

```
▾ test/
  ▾ Handler/
      CommentSpec.hs
      CommonSpec.hs
      HomeSpec.hs
      ProfileSpec.hs
```

``` HomeSpec.hs ``` の中身を見てみましょう。

```HomeSpec.hs
module Handler.HomeSpec (spec) where

import TestImport

spec :: Spec
spec = withApp $ do

    describe "Homepage" $ do
        it "loads the index and checks it looks right" $ do
          get HomeR
          statusIs 200
          htmlAnyContain "h1" "a modern framework for blazing fast websites"

          request $ do
              setMethod "POST"
              setUrl HomeR
              addToken
              fileByLabel "Choose a file" "test/Spec.hs" "text/plain" -- talk about self-reference
              byLabel "What's on the file?" "Some Content"

          statusIs 200
          -- more debugging printBody
          htmlAllContain ".upload-response" "text/plain"
          htmlAllContain ".upload-response" "Some Content"

        -- This is a simple example of using a database access in a test.  The
        -- test will succeed for a fresh scaffolded site with an empty database,
        -- but will fail on an existing database with a non-empty user table.
        it "leaves the user table empty" $ do
          get HomeR
          statusIs 200
          users <- runDB $ selectList ([] :: [Filter User]) []
          assertEq "user table empty" 0 $ length users
```

# 基本的な流れ

基本的には次のような流れでテストを書きます。

1. HTTPリクエスト[^1]を実行して
2. 結果を検証する
  * GETであれば、レスポンスのHTMLの内容を検証
  * POSTであれば、DBの内容を検証


# テスト用の設定

DBの接続先などの設定を変えたい場合は、``` config/test-settings.yml ``` を書き換えます。

# テストの実行

テストを実行するには次のコマンド[^2]を実行します。stackを使っている前提です。

```
stack test
```

# まとめ

非常に簡単にですが、Yesodにおけるテストを見てみました。
割と簡単にテストを書けると思います。便利ですね。


[^1]: ``` request ``` 関数がHTTPリクエストを投げる関数なのですが、これが実際にHTTPを投げているのか、それともエミュレートしているのか、分かりませんでした。。。
[^2]: 一部のテストを実行する方法が分かりませんでした。。。