我有一个班级:
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中找到任何看起来有用的东西。
答案 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)
请参阅@Martijn Pieters的回答。
长期解决方案是让客户端创建连接并将其交给DatabaseThing
使用。使用单一责任原则,在这种情况下,我认为DatabaseThing
不应负责建立连接。
这种技术可以减少依赖关系并为您提供更多灵活性,例如您可以设置一个连接池,并为每个新的DatabaseThing
实例提供一个来自池的连接,而无需更改DatabaseThing
中的任何内容。
答案 3 :(得分:1)
考虑到ibm_db_dbi
和lite
共享相同的接口,这应该可以解决问题:
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