【Streamlit】Streamlitのテスト用モジュール入門

この記事はGENDA Advent Calendar 2024の19日目の記事です。

https://qiita.com/advent-calendar/2024/genda

# はじめに
こんにちは。データエンジニアの山口歩夢です！

Streamlitにはたくさんの便利な関数が用意されていて、簡単にアプリケーションの開発ができますが、`AppTest`というテスト用にも便利なモジュールも用意されています。
こちらを活用することで、テストコードを非常に手軽に書くことができます。


テストコードを書くことで、ユーザーがウィジェットに入力した内容や出力値などを検証することができ、アプリケーションの保守性を高めることができます。
本記事では`pytest`と`AppTest`を組み合わせてテストを行いますが、`pytest`に限らず、様々なテストツールを使用できるようです。


https://docs.streamlit.io/develop/concepts/app-testing/get-started

今回は、「[Streamlit入門 Pythonで学ぶデータ可視化&アプリ開発ガイド](https://www.amazon.co.jp/Streamlit%E5%85%A5%E9%96%80-Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%83%BC%E3%82%BF%E5%8F%AF%E8%A6%96%E5%8C%96%EF%BC%86%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%82%AC%E3%82%A4%E3%83%89-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%B1%B1%E5%8F%A3-%E6%AD%A9%E5%A4%A2/dp/4295603511?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&ref_=fplfs&psc=1&smid=AN1VRQENFRJN5)」で紹介している以下のようなアプリケーションのテストコードを作成してみようと思います！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/dc921aa0-93ca-d949-7cb6-27a44f8fb39c.png)

細かくテストするというよりも、サイドバーのセレクトボックスやタイトル名、データフレームの数などをサクッと簡単にテストしてみます。



# 実装
それでは早速実装していきます！

## ptytestのインストール
まずは、`pytest`をインストールします。
pip installなどでインストールすることができます。

```cl
$ pip install pytest
```

## ファイルの用意
以下のような構成でディレクトリを用意します。
テストを書くファイルは、先頭または末尾に`test`と付くように命名します。
本記事では、`test_app.py`とします。

```
├── streamlit_app.py
└── tests/
    └── test_app.py
```

## テストコードの用意
それでは、テストコードを用意していきます。
アプリケーションの以下の要素に対してテストを行っていきます。

### サイドバー
  * `st.title`のテキスト
  * `st.selectbox`
    * labelのテキスト
    * 初期入力値
### メインエリア
  * `st.header`のテキスト
  * `st.dataframe`で出力しているデータフレームの数

結論、以下のようなテストコードを用意しました！
テスト用の関数にも`test_app`というように、先頭に`test`が付くように命名します。
```test_app.py
from streamlit.testing.v1 import AppTest


def test_app():
    # アプリをテストする準備
    at = AppTest.from_file("streamlit_app.py").run()

    # サイドバーのタイトルと選択ボックスのラベルを確認
    assert "製品の選択" in at.sidebar.title[0].value
    # selectbox ラベルの確認
    assert at.sidebar.selectbox[0].label == "製品大分類の選択"
    at.sidebar.selectbox[0].set_value("電子機器").run()  # 大分類
    assert at.sidebar.selectbox[1].label == "製品中分類の選択"
    at.sidebar.selectbox[1].set_value("コンピューター").run()  # 中分類
    assert at.sidebar.selectbox[2].label == "製品小分類の選択"
    at.sidebar.selectbox[2].set_value("ノートパソコン").run()  # 小分類

    assert "製品データ可視化ダッシュボード" in at.header[0].value

    # データフレームの確認
    assert len(at.dataframe) == 1
```
以下のようなコマンドを実行することで、上記のテストコードを使用してテストを行うことができます。
```cl
$ pytest test_app.py
```
テスト結果は以下のように表示されました。
`1 passed`と出力されているので、テストコード内の関数がテストを通過していることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/9b4f815d-aa31-f9f3-dc6e-5f5bf9788264.png)

### テストコードの解説


#### ライブラリのインポート
Streamlitには`AppTest`というテストコードを書くためのライブラリが用意されています。
以下のようにimportをして、こちらのライブラリを使用してテストコードを書いていきます。
```py
from streamlit.testing.v1 import AppTest
```

#### `AppTest`インスタンス初期化&アプリの実行
`AppTest`を使用してテストを行う場合、最初に`AppTest`のインスタンスを初期化する必要があります。
ドキュメントを読む限り、`AppTest.form_file()`を使用して、インスタンスを初期化することが推奨されていました。
こちらを使用すると、テスト対象のPythonスクリプト(`streamlit_app.py`)を指定して、テストを行うことができます。

そして、末尾に`run()`をつけることで、アプリケーションの実行を再現します。
```py
at = AppTest.from_file("streamlit_app.py").run()
```

#### st.titleのテキスト
サイドバーに`st.title`で出力しているタイトルのテキストは以下のようにテストをします。

アプリケーション内で最初の`st.title`をテストするため、`at.slidebar.title[0].value`と書いています。

もし、他にも`st.title`で出力しているタイトルがあれば、
`at.sidebar.title[1].value`、`at.sidebar.title[2].value`...のように、`title[n]`のnの数値を増やしていきます。
```py
assert "製品の選択" in at.sidebar.title[0].value
```

#### st.selectboxのラベル・初期入力値
サイドバーのセレクトボックスのラベルや初期入力値は、以下のようにテストをします。

出力しているセレクトボックスが複数あるため、
`at.sidebar.selectbox[1].value`、`at.sidebar.selectbox[2].value`...と`selectbox[n]のnの値が増えていっています。

```py
assert at.sidebar.selectbox[0].label == "製品大分類の選択"
at.sidebar.selectbox[0].set_value("電子機器").run()  # 大分類
assert at.sidebar.selectbox[1].label == "製品中分類の選択"
at.sidebar.selectbox[1].set_value("コンピューター").run()  # 中分類
assert at.sidebar.selectbox[2].label == "製品小分類の選択"
at.sidebar.selectbox[2].set_value("ノートパソコン").run()  # 小分類
```


#### st.headerのテキスト
メインエリアの`st.header`は以下のようにテストをします。
```py
assert "製品データ可視化ダッシュボード" in at.header[0].value
```

#### st.dataframeのデータフレームの出力数
st.dataframeで出力しているデータフレームの数は以下のようにテストしました。
```py
assert len(at.dataframe) == 1
```

#### その他
`st.file_uploader`などは、まだテスト用の関数が用意されておらず、順次追加予定の様子です。

https://docs.streamlit.io/develop/concepts/app-testing/cheat-sheet

# まとめ
Streamlitには、テスト用の関数が多く用意されていることが分かりました。
細かくテストをするというよりも、シンプル且つ高速にテストをするのに便利だなといった印象でした。

公式で用意されている、以下のチートシートを見ながらテストコードを書くのがおすすめです！

https://docs.streamlit.io/develop/concepts/app-testing/cheat-sheet

# 宣伝


12/6に「Streamlit入門 Pythonで学ぶデータ可視化&アプリ開発ガイド」という技術書を発売致しました。
Streamlitの基礎から、Streamlitを活用したアプリケーションの開発について、300ページにわたって解説させていただきました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/0777f3fa-be72-631e-21c5-4498dab092fc.png)




https://www.amazon.co.jp/Streamlit%E5%85%A5%E9%96%80-Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%83%BC%E3%82%BF%E5%8F%AF%E8%A6%96%E5%8C%96%EF%BC%86%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%82%AC%E3%82%A4%E3%83%89-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%B1%B1%E5%8F%A3-%E6%AD%A9%E5%A4%A2/dp/4295603511?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&ref_=fplfs&psc=1&smid=AN1VRQENFRJN5