🌐

[Web] Streamlitの簡易的なテストを作成した話

2022/12/18に公開

作成したサイト

レポジトリや作成した経緯は以下をご参照ください.

URL
Mahjong Util

Git Hub
https://github.com/ShunDeveloper/streamlit_app

作成した経緯
https://zenn.dev/shundeveloper/articles/f003e73658ce6f

Streamlitとは

StreamlitというPythonフレームワークを御存じでしょうか? Streamlitを使うと美しい表やグラフを使ったアプリケーションを簡単に構築できます. 私自身もユーザーとして利用
させていただいています. 興味を持たれた方は是非公式ドキュメントを確認して見てください.

Streamlit

今回はそんなStreamlitのアプリケーションにテストを追加してみた話です.

方針

公式の推奨する方法があればよいのですが, Documentを見たところ, 特に紹介されていませんでした. Googleで調べてみたところ, SeleniumBaseを使うと良いとのアドバイスがありましたが, 個人開発レベルの小さなアプリケーションにセットアップにコストのかかるSeleniumを使いたくなかったので以下の方針でテストをすることにしました.

  • 自作パッケージはPytestで単体でのテストを行う
  • アプリケーションはバックグランドで走らせてHTTPリクエストを投げて正しいリクエストを返すかテストをする

Pytestで自作パッケージの挙動をテスト

このレポジトリでは /page 以下で同じ処理を行うことがあり, それらの処理を /utls (以下 utlsパッケージ)で管理しています. この部分に関してはPytestコマンドでテストすることにしました. Pytestは pytest -s とコマンドを打つことでPython Scriptのprintをconsoleに出力することができるので書いています.

※4行目のsysの部分ですが, 上位階層のパッケージをimportする為に行っています.

import sys
import os

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from utils.is_na import is_na
from utils.question import question

def test_is_na_false():
    print()
    print('FUNCTION WORKING TEST: is_na (output must be false)')
    assert is_na(text="1000") == False
    print('='*30)

...

HTTPリクエストのステータスコードをチェック

先ほどのテストで自作パッケージのテストはできましたが, アプリケーションが動いているかの確認が出来ていません. そこでShell Scriptを使ってアプリケーションをbackgroudで走らせてそのうえでrequestメソッドを使ってHTTPリクエストを投げてみます.

echo 'start: running app [1/3]'
nohup streamlit run app.py > log.txt 2>&1 &
echo $! > save_pid.txt # save current pid
echo 'ok: running app [1/3]'

echo 'start: testing [2/3]'
pytest -s
echo 'ok: testing [2/3]'

echo 'start: shutdown [3/3]'
kill -9 `cat save_pid.txt`
rm save_pid.txt
echo 'ok: shutdown [3/3]'

[1/3]では, nohupコマンドでstreamlit run app.pyをbackgroud実行しています. これを行うことで[2/3]のコマンドを打つときもアプリケーションの実行がされている状態になります.

また, 3行目の $! > save_pid.txt では直近の実行されたプロセスのidをテキストファイルに保存しています. これはバックグランド実行したプロセスを kill するのにプロセスidが必要になるからです.

[2/3]では, pytestでアプリケーションにHTTPリクエストを投げています. 具体的には以下の処理を行っています.

import os

import requests
import time
import re

ROOT_URL = 'http://localhost:8501'

PAGE_PATH = "pages/"
PAGE_NAMES = []
tmp = os.listdir(PAGE_PATH) # get file name
assert 0 < len(tmp) # confirm
for i in range(len(tmp)):
    file_name = tmp[i]
    file_name = re.sub(r'[0-9]+_', '', file_name)
    file_name = re.sub(r'.py', '', file_name)
    PAGE_NAMES.append(file_name)

def test_app_root():
    print()
    print('WORKING TEST: root page')
    time.sleep(2)
    res = requests.get(ROOT_URL)
    res = str(res)
    assert '<Response [200]>' == res
    print('='*30)

def test_app_page():
    print('WORKING TEST: page under pages dir')
    for page_name in PAGE_NAMES:
        print('page name:', page_name)
        time.sleep(2)
        res = requests.get(ROOT_URL+'/'+page_name)
        res = str(res)
        assert '<Response [200]>' == res
    print('='*30)

def test_app_page():
    print('WORKING TEST: Not Found Request')
    time.sleep(2)
    res = requests.get(ROOT_URL+'/hoge')
    res = str(res)
    assert '<Response [404]>' == res
    print('='*30)

変数 PAGE_NAMES にはURLが保存されます. URLは /page 以下のファイル名から「数字_」と「.py」を抜いた文字に設定されるので正規表現で取得しています.

準備が出来たらrequestsメソッドでリクエストを投げてステータスコードを確認しています.

GitHub Actionsで自動実行

以下のURLを参照し, ymlファイルを作成しました. ローカルで使っていたPythonの環境を作成して前述したShell Scriptを動かします.

run-name: test-app
on:
  push:
    branches:
      - dev
  pull_request:
    branches:
      - dev
      - main
jobs:
  run-python:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-python@v4
      with:
        python-version: '3.8'
        cache: 'pip'
    - run: python -m pip install --upgrade pip
    - run: pip install -r requirements.txt
    - run: bash test/test.sh

コードに関しては以下のページを参考にしました. 詳しく知りたい方は以下のページを参照ください.

Building and testing Python - GitHub Docs
https://github.com/actions/setup-python

おわりに

ご意見ご感想等ありましたらTwitterかzennまでいただけると幸いです.

Discussion