使用monkeypatch测试Streamlit时遇“function object has no attribute conn”错误
问题描述
我正在为Streamlit应用编写测试,此前使用标准pytest和monkeypatch模拟PostgreSQL查询一切正常。但现在尝试从INSERT ... RETURNING ...;查询中获取数据时,mock的作用域始终设置不当。
项目结构
. ├── poetry.lock ├── pyproject.toml └── streamlit ├── app.py ├── classes │ ├── __init__.py │ └── burg.py └── tests ├── __init__.py └── test_app.py
核心代码
burg.py
import streamlit as st from sqlalchemy.sql import text class Burg: pass class Burger(Burg): @staticmethod def flip_burger() -> list[str]: with st.session_state.conn.session as session: new_antigens = session.execute( text( "INSERT INTO user_logs (update_time, antigens_id, users_id, " "site_page, status_changed) VALUES ('2024-10-10 00:00:00', 1, 1, " "'burgers', 'flipped') returning user_logs_id;" ) ) session.commit() new_ids = [str(new_id[0]) for new_id in new_antigens] return new_ids
app.py
import streamlit as st from classes.burg import Burger st.session_state.conn = st.connection("postgres") # init psql connection Burger.flip_burger()
测试代码(有问题版本)
import streamlit as st import pandas as pd from classes.burg import Burger class Test_Burger: def test_flip_burger(self, monkeypatch): class session_state: class conn: class session: def __enter__(self): return self def __exit__(self): pass @staticmethod def execute(null): return pd.DataFrame({"ids": [100]}) def mock_conn(*args, **kwargs): return session_state() monkeypatch.setattr(st, "session_state", mock_conn) borger = Burger() assert borger.flip_burger() == [100]
错误信息
FAILED tests/test_app.py::Test_Burger::test_flip_burger - AttributeError: 'function' object has no attribute 'conn'
我认为是mock的作用域或结构设计有误,请问如何用monkeypatch正确实现该模拟?
解决方案
错误原因
你把st.session_state替换成了一个函数mock_conn,但实际st.session_state是Streamlit提供的类似字典的状态对象,不是函数。当代码尝试访问st.session_state.conn时,自然会报错“函数对象没有conn属性”。
另外,原测试中用pd.DataFrame作为execute的返回值也有问题:SQLAlchemy的execute返回的是Result对象,迭代时会返回行元组;而直接迭代DataFrame会得到列名,导致new_ids生成错误。
修正后的测试代码
import streamlit as st from classes.burg import Burger class Test_Burger: def test_flip_burger(self, monkeypatch): # 模拟SQLAlchemy的Result行为:可迭代,返回包含ID的元组 class MockResult: def __iter__(self): yield (100,) # 对应原代码中new_id[0]取第一个元素 # 模拟session对象:上下文管理器 + execute/commit方法 class MockSession: def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass def execute(self, query): return MockResult() def commit(self): pass # 模拟提交,无需实际操作 # 模拟conn对象:包含session属性 class MockConn: session = MockSession() # 模拟session_state对象:包含conn属性 class MockSessionState: conn = MockConn() # 替换st.session_state为模拟对象,而不是函数 monkeypatch.setattr(st, "session_state", MockSessionState()) # 调用静态方法(无需实例化Burger,因为flip_burger是静态方法) result = Burger.flip_burger() assert result == ["100"] # 原代码返回的是str类型,所以要对比字符串
关键改动说明
- 修正session_state的模拟方式:直接用带有
conn属性的对象替换st.session_state,匹配Streamlit实际的状态结构,而非函数。 - 模拟SQLAlchemy Result行为:自定义
MockResult类,让它迭代时返回元组(100,),和原代码中new_id[0]的取值逻辑匹配。 - 完善session的方法:添加
commit方法(原代码中调用了session.commit(),必须模拟),同时确保上下文管理器的__enter__和__exit__正确实现。 - 修正断言逻辑:原代码中
new_ids是把ID转成字符串,所以断言要对比["100"]而不是[100]。
内容的提问来源于stack exchange,提问作者lunchbox7804
相关产品推荐
相关产品推荐

