You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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类型,所以要对比字符串

关键改动说明

  1. 修正session_state的模拟方式:直接用带有conn属性的对象替换st.session_state,匹配Streamlit实际的状态结构,而非函数。
  2. 模拟SQLAlchemy Result行为:自定义MockResult类,让它迭代时返回元组(100,),和原代码中new_id[0]的取值逻辑匹配。
  3. 完善session的方法:添加commit方法(原代码中调用了session.commit(),必须模拟),同时确保上下文管理器的__enter__和__exit__正确实现。
  4. 修正断言逻辑:原代码中new_ids是把ID转成字符串,所以断言要对比["100"]而不是[100]。

内容的提问来源于stack exchange,提问作者lunchbox7804

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 03:50:10