我正在尝试使用 peewee python ORM 实现多对多场景,我想要一些单元测试。Peewee 教程很棒,但它假设数据库是在模块级别定义的,然后所有模型都在使用它。我的情况不同:我没有明确运行的测试的源代码文件(从python的角度来看的模块),我正在使用从该文件收集测试并运行它们的nose。
如何仅将自定义数据库用于在测试中实例化的模型(由鼻子运行)?我的目标是仅使用内存数据库进行测试,以加快测试过程。
我今天刚刚推送了一个提交,这使得这更容易。
该修复采用上下文管理器的形式,它允许您覆盖模型的数据库:
from unittest import TestCase
from playhouse.test_utils import test_database
from peewee import *
from my_app.models import User, Tweet
test_db = SqliteDatabase(':memory:')
class TestUsersTweets(TestCase):
def create_test_data(self):
# ... create a bunch of users and tweets
for i in range(10):
User.create(username='user-%d' % i)
def test_timeline(self):
with test_database(test_db, (User, Tweet)):
# This data will be created in `test_db`
self.create_test_data()
# Perform assertions on test data inside ctx manager.
self.assertEqual(Tweet.timeline('user-0') [...])
# once we exit the context manager, we're back to using the normal database
请参阅文档并查看示例测试用例:
要不在每个测试用例中包含上下文管理器,请覆盖run
方法。
# imports and db declaration
class TestUsersTweets(TestCase):
def run(self, result=None):
with test_database(test_db, (User, Tweet)):
super(TestUsersTweets, self).run(result)
def test_timeline(self):
self.create_test_data()
self.assertEqual(Tweet.timeline('user-0') [...])
我从@coleifer 和@avalanchy 那里得到了很好的答案,并将它们更进一步。
为了避免在每个TestCase
子类上覆盖 run 方法,您可以使用基类......而且我也喜欢不必写下我使用的每个模型类的想法,所以我想出了这个
import unittest
import inspect
import sys
import peewee
from abc import ABCMeta
from playhouse.test_utils import test_database
from business_logic.models import *
test_db = peewee.SqliteDatabase(':memory:')
class TestCaseWithPeewee(unittest.TestCase):
"""
This abstract class is used to "inject" the test database so that the tests don't use the real sqlite db
"""
__metaclass__ = ABCMeta
def run(self, result=None):
model_classes = [m[1] for m in inspect.getmembers(sys.modules['business_logic.models'], inspect.isclass) if
issubclass(m[1], peewee.Model) and m[1] != peewee.Model]
with test_database(test_db, model_classes):
super(TestCaseWithPeewee, self).run(result)
所以,现在我可以继承自TestCaseWithPeewee
,而不必担心除了测试之外的任何其他事情
使用test_database
时遇到test_db
未初始化的问题:
nose.proxy.Exception: Error, database not properly initialized before opening connection
-------------------- >> begin captured logging << --------------------
peewee: DEBUG: ('SELECT "t1"."id", "t1"."name", "t1"."count" FROM "counter" AS t1', [])
--------------------- >> end captured logging << ---------------------
我最终通过create_tables=True
像这样传递来解决这个问题:
def test_timeline(self):
with test_database(test_db, (User, Tweet), create_tables=True):
# This data will be created in `test_db`
self.create_test_data()
根据文档 create_tables
应该默认为,True
但在最新版本的peewee
.
显然,对于所描述的场景有一种新方法,您可以在setUp()
测试用例的方法中绑定模型:
来自官方文档的示例:
# tests.py
import unittest
from my_app.models import EventLog, Relationship, Tweet, User
MODELS = [User, Tweet, EventLog, Relationship]
# use an in-memory SQLite for tests.
test_db = SqliteDatabase(':memory:')
class BaseTestCase(unittest.TestCase):
def setUp(self):
# Bind model classes to test db. Since we have a complete list of
# all models, we do not need to recursively bind dependencies.
test_db.bind(MODELS, bind_refs=False, bind_backrefs=False)
test_db.connect()
test_db.create_tables(MODELS)
def tearDown(self):
# Not strictly necessary since SQLite in-memory databases only live
# for the duration of the connection, and in the next step we close
# the connection...but a good practice all the same.
test_db.drop_tables(MODELS)
# Close connection to db.
test_db.close()
# If we wanted, we could re-bind the models to their original
# database here. But for tests this is probably not necessary.
对于使用 pytest 的任何人,我都是这样做的:
conftest.py
MODELS = [User, Tweet] # Also add get_through_model() for ManyToMany fields
test_db = SqliteDatabase(':memory:')
test_db.bind(MODELS, bind_refs=False, bind_backrefs=False)
test_db.connect()
test_db.create_tables(MODELS)
@pytest.fixture(autouse=True)
def in_mem_db(mocker):
mocked_db = mocker.patch("database.db", autospec=True) # "database.db" is where your app's code imports db from
mocked_db.return_value = test_db
return mocked_db
瞧,您的所有测试都使用内存中的 sqlite 数据库运行。