使用Pytest测试时Mock sqlbuilder.func.FROM_UNIXTIME失效问题排查
解决SQLObject中Mock
sqlbuilder.func.FROM_UNIXTIME 生成异常SQL的问题 我明白你遇到的问题:Mock TO_BASE64 能正常工作,但Mock FROM_UNIXTIME 后,WHERE条件里直接变成了 AND (1),而不是期望的strftime语句。这背后的原因是你替换的函数返回的是普通字符串,而SQLObject会把这个字符串当成Python布尔表达式求值(非空字符串在Python中是True,对应SQL里的1),而不是把它当作SQL片段处理。
问题根源
sqlbuilder.func下的函数并不是普通的Python函数,它们是SQLObject用来构建SQL表达式的特殊对象。当你直接用返回字符串的函数替换FROM_UNIXTIME时,SQLObject在解析WHERE条件时,会把你的函数返回值当作Python表达式处理,而非SQL语法的一部分。而TO_BASE64能工作是因为你返回的是原SQLObject列对象(Store.q.sample),这个对象SQLObject能正确识别并转换为SQL。
解决方案
你需要让Mock的FROM_UNIXTIME返回SQLObject能识别的SQL表达式对象,而不是普通字符串。可以用sqlbuilder.CustomSQL来直接封装你的SQL片段,或者用SQLObject原生的函数构建方式来生成对应的SQL。
修改后的测试代码(test_ex1.py)
import pytest from sqlobject import sqlbuilder from models import retrieve try: import mock from mock import MagicMock except ImportError: from unittest import mock from unittest.mock import MagicMock def TO_BASE64(x): return x def FROM_UNIXTIME(x, y): # 使用CustomSQL封装SQL片段,x会被自动转换为对应的SQL列/值 return sqlbuilder.CustomSQL( 'strftime("%Y%m%d", datetime(%s, "unixepoch", "localtime"))', args=[x] ) @mock.patch("sqlobject.sqlbuilder.func.TO_BASE64", TO_BASE64) @mock.patch("sqlobject.sqlbuilder.func.FROM_UNIXTIME", FROM_UNIXTIME) def test_retrieve(): result = retrieve('Some') assert result == []
修改后的可运行示例
#! /usr/bin/env python from sqlobject import * __connection__ = "sqlite:/:memory:?debug=1&debugOutput=1" try: import mock from mock import MagicMock except ImportError: from unittest import mock from unittest.mock import MagicMock class Store(SQLObject): name = StringCol() sample = BLOBCol() createdAt = TimestampCol() Store.createTable() DATE_FORMAT = "%Y-%m-%d" def retrieve(name): query = sqlbuilder.Select([ sqlbuilder.func.TO_BASE64(Store.q.sample), ], sqlbuilder.AND( Store.q.name == name, sqlbuilder.func.FROM_UNIXTIME(Store.q.createdAt, DATE_FORMAT) >= sqlbuilder.func.FROM_UNIXTIME("2018-10-12", DATE_FORMAT) )) connection = Store._connection query = connection.sqlrepr(query) print("生成的SQL:", query) queryResult = connection.queryAll(query) return queryResult def TO_BASE64(x): return x def FROM_UNIXTIME(x, y): return sqlbuilder.CustomSQL( 'strftime("%Y%m%d", datetime(%s, "unixepoch", "localtime"))', args=[x] ) for p in [ mock.patch("sqlobject.sqlbuilder.func.TO_BASE64",TO_BASE64), mock.patch("sqlobject.sqlbuilder.func.FROM_UNIXTIME",FROM_UNIXTIME), ]: p.start() retrieve('Some') mock.patch.stopall()
效果验证
现在运行代码,生成的SQL会和你期望的一致:
SELECT store.sample FROM store WHERE (((store.name) = ('Some')) AND (strftime("%Y%m%d", datetime(store.created_at, "unixepoch", "localtime")) >= strftime("%Y%m%d", datetime('2018-10-12', "unixepoch", "localtime"))))
额外说明
如果不想用CustomSQL,也可以用SQLObject原生的函数构造方式来模拟,比如:
def FROM_UNIXTIME(x, y): return sqlbuilder.func.strftime( "%Y%m%d", sqlbuilder.func.datetime(x, "unixepoch", "localtime") )
这种方式更符合SQLObject的原生用法,也能生成正确的SQL。
内容的提问来源于stack exchange,提问作者Chandan
相关产品推荐
相关产品推荐

