TEPAでシナリオ駆動開発

さくらちゃんは[さくらちゃん](https://twitter.com/arowM_)やぎぃ

これは[Elm Meetup](https://elm-jp.connpass.com/event/262458/)の発表資料です。

---

あなたのそのアプリ、データ中心設計になってませんか？

---

## データ中心設計

内部的なデータ構造にしたがっただけの設計

* メリット: 実装者的に設計・実装が楽
* デメリット: 使いにくいのでユーザーがキレる

---

## データ中心設計の代表例

国税庁のシステム "e-Tax" というデータ中心設計のよくできた実例

以下の機能が**独立した別の機能**として提供されている

* ②申告・申請データの作成
* ③申告・申請データへ電子署名
* ④申告・申請データの送信

データへの操作で設計すんなよ！ さくらちゃんにとっては一連の操作なんだよ！💢🐐

cf., [ご利用の流れ](https://www.e-tax.nta.go.jp/hojin.html#flow04)

---

「紙の書類と同じ形式のデータが必要」というお役所的「データ」の都合で設計されている

* 「別表六(二)付表五」とかいう意味わからん書式名からユーザーが自分で帳票を選択
    * さくらちゃんは決算処理をしたいんだよ！ どれ選べばいいんだよ！💢🐐
* 「帳票」をそのまま書類データの形で編集する
    * なんで紙形式と同じことをパソコンでやるんだよ！ だったら手書きするよ！💢🐐
![Screenshot at 2022-11-16 12-37-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16345/d793fa41-7e40-fbb1-4ca1-569a5d7e5679.png)

cf., [マニュアル](https://www.e-tax.nta.go.jp/manual/manual17.pdf)

---

データ中心では、数億円かけて腐敗したうんこが作られる

→ ユーザー中心設計をしましょう

---

## ユーザー中心設計

ユーザーが何をしたいか、ユーザーに何をさせたいかを中心とした設計

* メリット: ユーザーが継続的に使ってくれる
* デメリット: 人間さんの心がわからない人間さんには設計が困難

---

## シナリオ駆動開発

脳ミソポカーンで設計するとデータ中心設計になっちゃいます
それを防ぐために「_ユースケースシナリオ_」をつくるところからはじめましょう

シナリオ駆動開発とはたったそれだけのこと

---

## ユースケースシナリオとは

* 想定する利用者の姿を具体的に思い浮かべます
* その利用者の霊をその身に降ろします
* 未来にトリップして完成後のシステムを操作します
* その様子を文章で残します

ユースケースシナリオとはたったそれだけのこと

cf., [片手間にはじめるUXデザイン](https://qiita.com/arowM/items/cda86ffba94834534732)

---

## ユースケースシナリオの例

* さくらちゃん: さくらちゃんは世界一かわいいヤギさんやぎぃ
* ワルナスビくん: ワルナスビくんはワルナスビの妖精ワルゥ
* ワルナスビくん: このトゲトゲとちょっとした毒に注意さ🍴
* さくらちゃん: はい、ということで、今日は、GMS(Goat Management Service)をさわっていきたいと思います (YouTubeみたいな効果音)
* さくらちゃん: GMSは世界のヤギさん情報を管理するサービスだよ
* さくらちゃん: (URLをブラウザーに入力した)
    * ログインページが表示される
    * 最初はエラーが表示されていない
* さくらちゃん: ふ〜ん、ログインが必要ってわけね
* さくらちゃん: そういえばお父さんがアカウント情報を書いたメモを用意してくれてたはず
* ワルナスビくん: さくらちゃん、これじゃない？😈
* さくらちゃん: ありがとう🌸
* さくらちゃん: (ログインID入力欄に "guest" と入力した)
* さくらちゃん: え〜と、メモによるとパスワードは空のままでいいみたいだね！
* さくらちゃん: (「ログイン」ボタンをクリックした)
    * ログインフォームにエラーが表示される: 「パスワードが入力されていません」
* ワルナスビくん: ナスビビビッ😈 さっき渡したのは偽物のメモワルゥ
* さくらちゃん: なんでそんなことするの...？
* ワルナスビくん: ごめんごめん、ちょっとしたイタズラワルゥ
* さくらちゃん: 食べちゃうよ？
* ワルナスビくん: え...
* ワルナスビくん: トゲトゲあるよ？
* さくらちゃん: ヤギさんの口の強靭さナメてる？
* ワルナスビくん: あ、でも毒があるからお腹いたいいたいだよ？
* さくらちゃん: じゃあやめる！
* ワルナスビくん: ふぅ... 持ってて良かったソラニン...
* ワルナスビくん: こっちが本当のメモだよ
* さくらちゃん: さくらちゃん入力する！
* さくらちゃん: (パスワード入力欄に "fuestPass" と入力した)
    * ログインフォームに表示されていたエラーがなくなる
* さくらちゃん: よさそう
* さくらちゃん: (「ログイン」ボタンをクリックした)
    * ログインフォームに「IDまたはパスワードが間違っています」と表示された
* さくらちゃん: えぇ...
* ワルナスビくん: さくらちゃん、もしかしたらパスワード打ち間違えてるかもよ？
* さくらちゃん: あ、そうかも。偶蹄類は指が2本しかないからキーボード使いにくいんだよね
* さくらちゃん: (パスワード入力欄に "guestPass" と入力した)
    * トップページにリダイレクトされる
* さくらちゃん: ぶっめいぇ〜い！

---

## TEAはデータ中心

TEAはデータ中心設計なアーキテクチャー:
**現在** のモデルとメッセージから次のモデルとコマンドを決定するだけ

→ 時系列形式のシナリオとは相性が悪い

---

## そこでTEPA

TEPA: The Elm Procedure Architecture

* さくらちゃんがつくってる（動くとこまでできてる）
* TEA上に構築されたフレームワーク
* 時系列に沿った記述でアプリを実装できる

---

```elm
loginFormProcedure : Bucket -> Promise Void
loginFormProcedure bucket =
    let
        modifyForm f = ...
    in
    -- ユーザーの操作を待つ
    Tepa.withLayerEvent <|
        \e ->
            case e of
                ChangeLoginId str ->
                    [ modifyForm <|
                        \m -> { m | id = str }
                    , Tepa.lazy <|
                        \_ -> loginFormProcedure bucket
                    ]

                ChangeLoginPass str ->
                    [ modifyForm <|
                        \m -> { m | pass = str }
                    , Tepa.lazy <| \_ -> loginFormProcedure bucket
                    ]

                ClickSubmitLogin ->
                    [ Tepa.lazy <|
                        \_ ->
                            submitLoginProcedure bucket
                    ]

                _ ->
                    []


submitLoginProcedure : Bucket -> Promise Void
submitLoginProcedure bucket =
    let
        modifyLoginForm f = ...
    in
    Tepa.sequence
        [ modifyLoginForm <|
            \m -> { m | isBusy = True }
        , Tepa.currentState
            |> Tepa.andThen
                (\curr ->
                    case Login.fromForm curr.loginForm.form of
                        Err _ ->
                            Tepa.sequence
                                [ modifyLoginForm <|
                                    \m ->
                                        { m
                                            | isBusy = False
                                            , showError = True
                                        }
                                , Tepa.lazy <| \_ -> loginFormProcedure bucket
                                ]

                        Ok login ->
                            requestLogin login
                                |> Tepa.andThen
                                    (\response ->
                                        case response of
                                            Err err ->
                                                ...

                                            Ok resp ->
                                                ...
                                    )
                )
        ]

```

---

## 待って

TEAってテストしやすいように状態を分離した純粋関数にしてるんでしょ？

時系列で状態を持っちゃったら、テストしにくいじゃん！

---

## 安心して

TEPAはアプリケーションのテストもサポートしてます。

一連の操作が正しく処理されるかは、むしろTEAよりもテストしやすい！

---

## TEPAのシナリオ機能

TEPAはシナリオを作成してドキュメント出力する機能があります。

なんと、そのシナリオからアプリケーションのテストを自動生成できます！
シナリオに沿った操作ができないとテストでエラーが表示されます。

---

こんなふうにシナリオを書くよ！
（時間がないから気になる人はあとで見てね）

```elm
introduction1 : List Scenario
introduction1 =
    [ userComment sakuraChan "Hi. I'm Sakura-chan, the cutest goat girl in the world."
    , userComment sakuraChan "Today I'll try a goat management service."
    , userComment sakuraChan "I'll try to access the URL."
    , Scenario.loadApp sakuraChanMainSession
        "Load the home page."
        { route =
            { path = "tepa/"
            , query = Nothing
            , fragment = Nothing
            }
        , flags = JE.object []
        }
    , onSakuraChanMainSession.app.receiveSession <|
        Err (Http.BadStatus 403)
    , onSakuraChanMainSession.login.expectAvailable
        "Displays login page."
    , userComment sakuraChan
        "I see I need to log in! I remember my dad gave me the account information in advance."
    , onSakuraChanMainSession.login.expectLoginFormShowNoErrors
        "The login form shows no errors at first."
    , userComment yabugarashiKun
        "I'm Yabugarashi-kun. I'm going play a prank on Sakura-chan. Muahahahahaha! 😈"
    , userComment yabugarashiKun
        "Sakura-chan, here is the account information note your father gave you. 😈"
    , userComment sakuraChan
        "Thanks, Yabugarashi-kun. 🌸"
    , onSakuraChanMainSession.login.changeLoginId "guest"
    , userComment sakuraChan
        "The note says that the password can be left blank."
    , onSakuraChanMainSession.login.clickSubmitLogin
    , onSakuraChanMainSession.login.expectLoginFormShowError
        "Password is required."
    , userComment sakuraChan
        "Oh my goat, I got an error..."
    , userComment yabugarashiKun
        "Sorry, sorry, I just got a little naughty. Here's the real note."
    , userComment sakuraChan
        "OK, I'll try again."
    , onSakuraChanMainSession.login.changeLoginPass "fuestPass"
    , onSakuraChanMainSession.login.expectLoginFormShowNoErrors
        "The login form shows no errors at this point."
    , userComment sakuraChan
        "It looks good."
    , onSakuraChanMainSession.login.clickSubmitLogin
    , onSakuraChanMainSession.login.receiveLoginResp <|
        Err (Http.BadStatus 401)
    , onSakuraChanMainSession.login.toast.expectErrorMessage
        { message = "Incorrect ID or Password." }
        "Toast popup shows error: \"Incorrect ID or Password.\""
    , userComment sakuraChan "Oops!"
    , onSakuraChanMainSession.login.toast.awaitAllToDisappear
    , onSakuraChanMainSession.login.toast.expectNoMessages
        "No error popup messages now."
    , userComment yabugarashiKun "Maybe you typed the password wrong."
    , userComment sakuraChan "That may be true. It's hard to type with my two-fingered hooves..."
    , onSakuraChanMainSession.login.changeLoginPass "guestPass"
    , onSakuraChanMainSession.login.clickSubmitLogin
    , Scenario.fromOk "Sample response"
        (JD.decodeString JD.value """
            {
              "profile": {
                "id": "Sakura-chan-ID"
              }
            }
          """)
      <|
        \resp ->
            [ onSakuraChanMainSession.login.receiveLoginResp <| Ok resp
            ]
    , onSakuraChanMainSession.home.expectAvailable
        "Redirect to home page."
    , userComment sakuraChan "Yes!"
    , Scenario.cases
        [ Scenario.section "Home page #1" pageHomeCase1
        , Scenario.section "Home page #2" pageHomeCase2
        ]
    ]
```

---

デモタイム
（そんな時間ないよ）

---

## リリースは？

だいたいできてるけど、もっと洗練させてからリリースするよ
ソースはGitHubに置いてあるから興味がある人は先行プレイできるよ

[TEPAのリポジトリー](https://github.com/arowM/tepa)

---

またね！