使用peewee ORM对代码进行单元测试的自定义sqlite数据库

时间:2013-04-13 00:22:47

标签: python sqlite peewee

我正在尝试使用peewee python ORM实现多对多场景,我想要一些单元测试。 Peewee教程很棒,但它假设数据库是在模块级别定义的,然后所有模型都在使用它。我的情况不同:我没有源代码文件(来自python的观点的模块)和我明确运行的测试,我使用的是从该文件收集测试并运行它们的鼻子。

如何仅将自定义数据库用于测试中实例化的模型(由鼻子运行)?我的目标是仅使用内存数据库进行测试,以加快测试过程。

5 个答案:

答案 0 :(得分:14)

我今天刚刚提交了一个提交,这使得这更容易。

修复程序采用上下文管理器的形式,允许您覆盖模型的数据库:

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

请参阅documentation并查看示例测试用例:

答案 1 :(得分:7)

要在每个测试用例中不包含上下文管理器,请覆盖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') [...])

答案 2 :(得分:5)

我从@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而不必担心除了测试之外的任何其他事情

答案 3 :(得分:0)

使用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()

根据the docs create_tables,默认为True,但最新版本的peewee似乎并非如此。

答案 4 :(得分:0)

显然,对于上述情况,有一种新方法,您可以在测试用例的setUp()方法中绑定模型:

the official docs中的示例:

# 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.