模拟__init __()进行单元测试

时间:2013-07-24 14:28:37

标签: python sqlite unit-testing mocking

我有一个班级:

class DatabaseThing():
     def __init__(self, dbName, user, password):
          self.connection = ibm_db_dbi.connect(dbName, user, password)

我想测试这个类但是测试数据库。所以在我的测试课中我是这样的:

import sqlite3 as lite
import unittest
from DatabaseThing import *

class DatabaseThingTestCase(unittest.TestCase):

    def setUp(self):
        self.connection = lite.connect(":memory:")
        self.cur = self.connection.cursor()
        self.cur.executescript ('''CREATE TABLE APPLE (VERSION INT, AMNT SMALLINT);
            INSERT INTO APPLE VALUES(16,0);
            INSERT INTO APPLE VALUES(17,5);
            INSERT INTO APPLE VALUES(18,1);
            INSERT INTO APPLE VALUES(19,15);
            INSERT INTO APPLE VALUES(20,20);
            INSERT INTO APPLE VALUES(21,25);''')

如何使用此连接而不是我要测试的类的连接?这意味着使用setUp(self)的连接而不是DatabaseThing的连接。我无法在不实例化类的情况下测试函数。我想在Test Class中以某种方式模拟__init__方法,但我没有在documentation中找到任何看起来有用的东西。

5 个答案:

答案 0 :(得分:35)

您可以简单地对数据库类进行子类化并对其进行测试,而不是模拟:

class TestingDatabaseThing(DatabaseThing):
     def __init__(self, connection):
          self.connection = connection

并为您的测试实例化那个类而不是DatabaseThing。方法仍然相同,行为仍然相同,但现在使用self.connection的所有方法都使用测试提供的连接。

答案 1 :(得分:2)

尝试替换凌乱,脆弱和hacky的 init 函数,尝试将函数传递给数据库构造函数,如下所示:

# Test connection creation
def connect_lite(dbName=None, user=None, password=None):
    connection = lite.connect(":memory:")
    cur = self.connection.cursor()
    cur.executescript ('''CREATE TABLE APPLE (VERSION INT, AMNT SMALLINT);
                          INSERT INTO APPLE VALUES(16,0);
                          INSERT INTO APPLE VALUES(17,5);
                          INSERT INTO APPLE VALUES(18,1);
                          INSERT INTO APPLE VALUES(19,15);
                          INSERT INTO APPLE VALUES(20,20);
                          INSERT INTO APPLE VALUES(21,25);''')
    return cur


# Production connection creation
def connect_ibm(dbName, user, password):
    return ibm_db_dbi.connect(dbName, user, password)

# Your DatabaseThing becomes:
class DatabaseThing():
    def __init__(self, connect, dbName, user, password):
        self.connection = connect(dbName, user, password)

# In your test create a DatabaseThing
t = DatabaseThing(connect_lite, dbName, user, password)

# In your production code create a DatabaseThing
p = DatabaseThing(connect_ibm, dbName, user, password)      

这样做的另一个好处是可以将代码与您正在使用的数据库技术略微分离。

答案 2 :(得分:2)

方法1:子类

请参阅@Martijn Pieters的回答。

方法2:控制反转

长期解决方案是让客户端创建连接并将其交给DatabaseThing使用。使用单一责任原则,在这种情况下,我认为DatabaseThing不应负责建立连接。

这种技术可以减少依赖关系并为您提供更多灵活性,例如您可以设置一个连接池,并为每个新的DatabaseThing实例提供一个来自池的连接,而无需更改DatabaseThing中的任何内容。

答案 3 :(得分:1)

考虑到ibm_db_dbilite共享相同的接口,这应该可以解决问题:

import mock
import sqlite3 as lite

class DatabaseThingTestCase(unittest.TestCase):

    def setUp(self):
        self.patch = mock.patch('module_with_DatabaseThing_definition.ibm_db_dbi', lite)
        self.patch.start()

    def tearDown(self):
        self.patch.stop()

即。您的DatabaseThing文件名为database/things.py,然后该修补程序将显示为database.things.ibm_db_dbi

模仿的例子:

<强> moduleA.py

def connection(*args):
    print 'The original connection. My args', args

<强> moduleB.py

def connection(*args):
    print 'The mocked connection. My args', args

<强> myClass.py

import moduleA


class MyClass(object):
    def __init__(self):
        self.connection = moduleA.connection('Test', 'Connection')

<强> test.py

import mock
import moduleB

from myClass import MyClass


def regular_call():
    MyClass()


def mocked_call():
    def wrapped_connection(*args):
        return moduleB.connection(':memory:')

    my_mock = mock.Mock(wraps=moduleB)
    my_mock.connection = wrapped_connection
    with mock.patch('myClass.moduleA', my_mock):
        MyClass()

    MyClass()

regular_call()
mocked_call()

运行 test.py 会给出:

The original connection. My args ('Test', 'Connection')
The mocked connection. My args (':memory:',)
The original connection. My args ('Test', 'Connection')

答案 4 :(得分:0)

您应该使用mock包来模拟类的方法__init__

from mock import patch


def test_database_thing(self):
    def __init__(self, dbName, user, password):
        # do something else
    with patch.object(DatabaseThing, '__init__', __init__):
        # assert something