App testing example
Testing a login page
让我们考虑一个登录页面。在这个例子中,secrets.toml
不存在。我们将在测试中手动声明虚拟秘密。为了避免时间攻击,登录脚本使用hmac
来比较用户的密码和秘密值,这是一种安全最佳实践。
Project summary
登录页面行为
在深入研究应用程序的代码之前,让我们思考一下这个页面应该做什么。无论你是使用测试驱动开发还是在编写代码后编写单元测试,思考需要测试的功能都是一个好主意。登录页面的行为应如下:
- 在用户与应用交互之前:
- 他们的状态是“未验证”。
- 显示密码提示。
- 如果用户输入了错误的密码:
- 他们的状态为“错误”。
- 显示错误信息。
- 输入的密码尝试被清除。
- 如果用户输入了正确的密码:
- 他们的状态为“已验证”。
- 显示确认消息。
- 显示注销按钮(无需登录提示)。
- 如果已登录用户点击Log out按钮:
- 他们的状态为“未验证”。
- 会显示密码提示。
登录页面项目结构
myproject/
├── app.py
└── tests/
└── test_app.py
登录页面 Python 文件
页面规范中提到的用户状态被编码在st.session_state.status
中。该值在脚本开始时初始化为“unverified”,并在密码提示接收到新条目时通过回调更新。
"""app.py"""
import streamlit as st
import hmac
st.session_state.status = st.session_state.get("status", "unverified")
st.title("My login page")
def check_password():
if hmac.compare_digest(st.session_state.password, st.secrets.password):
st.session_state.status = "verified"
else:
st.session_state.status = "incorrect"
st.session_state.password = ""
def login_prompt():
st.text_input("Enter password:", key="password", on_change=check_password)
if st.session_state.status == "incorrect":
st.warning("Incorrect password. Please try again.")
def logout():
st.session_state.status = "unverified"
def welcome():
st.success("Login successful.")
st.button("Log out", on_click=logout)
if st.session_state.status != "verified":
login_prompt()
st.stop()
welcome()
登录页面测试文件
这些测试严格遵循上述应用程序的规范。在每个测试中,在运行应用程序并进行进一步的模拟和检查之前,会设置一个虚拟的秘密。
from streamlit.testing.v1 import AppTest
def test_no_interaction():
at = AppTest.from_file("app.py")
at.secrets["password"] = "streamlit"
at.run()
assert at.session_state["status"] == "unverified"
assert len(at.text_input) == 1
assert len(at.warning) == 0
assert len(at.success) == 0
assert len(at.button) == 0
assert at.text_input[0].value == ""
def test_incorrect_password():
at = AppTest.from_file("app.py")
at.secrets["password"] = "streamlit"
at.run()
at.text_input[0].input("balloon").run()
assert at.session_state["status"] == "incorrect"
assert len(at.text_input) == 1
assert len(at.warning) == 1
assert len(at.success) == 0
assert len(at.button) == 0
assert at.text_input[0].value == ""
assert "Incorrect password" in at.warning[0].value
def test_correct_password():
at = AppTest.from_file("app.py")
at.secrets["password"] = "streamlit"
at.run()
at.text_input[0].input("streamlit").run()
assert at.session_state["status"] == "verified"
assert len(at.text_input) == 0
assert len(at.warning) == 0
assert len(at.success) == 1
assert len(at.button) == 1
assert "Login successful" in at.success[0].value
assert at.button[0].label == "Log out"
def test_log_out():
at = AppTest.from_file("app.py")
at.secrets["password"] = "streamlit"
at.session_state["status"] = "verified"
at.run()
at.button[0].click().run()
assert at.session_state["status"] == "unverified"
assert len(at.text_input) == 1
assert len(at.warning) == 0
assert len(at.success) == 0
assert len(at.button) == 0
assert at.text_input[0].value == ""
看到在上一个测试中会话状态是如何被修改的吗?测试没有完全模拟用户登录,而是通过设置at.session_state["status"] = "verified"
直接跳转到已登录状态。运行应用程序后,测试继续模拟用户注销。
Automating your tests
如果 myproject/
被推送到 GitHub 作为一个仓库,你可以使用 Streamlit App Action 添加 GitHub Actions 测试自动化。这就像在 myproject/.github/workflows/
添加一个工作流文件一样简单:
# .github/workflows/streamlit-app.yml
name: Streamlit app
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
streamlit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: streamlit/streamlit-app-action@v0.0.3
with:
app-path: app.py
还有问题吗?
我们的 论坛 充满了有用的信息和Streamlit专家。