我有课:
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
的连接。如果不实例化该类,我无法测试这些函数。我想在测试类中以某种方式模拟 __init__
方法,但我没有在 documentation 中找到任何看起来有用的东西。
您可以简单地对数据库类进行子类化并对其进行测试,而不是进行模拟:
class TestingDatabaseThing(DatabaseThing):
def __init__(self, connection):
self.connection = connection
并实例化 that 类而不是
DatabaseThing
进行测试。方法仍然相同,行为仍然相同,但现在所有使用 self.connection
的方法都使用您测试提供的连接。
如果您想在初始化类时返回模拟,请模拟 __new__ 方法,而不是 init。
new创建新实例并init初始化它,但只能返回None。
如果您模拟 new,它可以返回一个模拟,您可以断言以模拟测试中的实例创建。
@mock.patch('Car.__new__')
def test_create_car(self, mock_Car):
mock_inst = mock.MagickMock()
mock_Car.return_value = mock_inst
create_car()
# Assert class was called
mock_Car.assert_called_once()
# __new__ is called with actual class as first arg
mock_Car.assert_called_with(Car)
# Assert instance method is called as expected
mock_inst.set_miles.assert_called_with(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
请参考@Martijn Pieters的回答。
一个长期的解决方案是让客户端创建连接并将其交给
DatabaseThing
使用。使用单一职责原则,我认为 DatabaseThing
不应该负责在这种情况下建立连接。
这种技术减少了依赖性并为您提供了更多的灵活性,例如您可以设置一个连接池,并为
DatabaseThing
的每个新实例提供池中的连接,而无需更改 DatabaseThing
中的任何内容。
不要尝试替换混乱、脆弱且老套的 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)
这样做的好处是可以将代码与正在使用的数据库技术稍微解耦。
考虑到
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
。
嘲笑的例子:
模块A.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')
测试.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')
@mock.patch('汽车.new') def test_create_car(self,mock_Car): 模拟安装 = 模拟.MagickMock() 模拟_Car.return_value = 模拟_inst
create_car()
# Assert class was called
mock_Car.assert_called_once()
# __new__ is called with actual class as first arg
mock_Car.assert_called_with(Car)
# Assert instance method is called as expected
mock_inst.set_miles.assert_called_with(0)
考虑到 ibm_db_dbi 和 lite 共享相同的接口,这应该可以解决问题:
导入模拟 将 sqlite3 导入为 lite
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() I have a class:
类DatabaseThing(): def init(self, dbName, 用户, 密码): self.connection = ibm_db_dbi.connect(dbName, 用户, 密码) 我想测试这个类,但使用测试数据库。所以在我的测试课中我正在做这样的事情:
导入 sqlite3 作为精简版 导入单元测试 从 DatabaseThing 导入 *
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 的连接。如果不实例化该类,我无法测试这些函数。我想在测试类中以某种方式模拟 init 方法,但我没有在文档中找到任何看起来有用的东西。
pythonsqlite单元测试模拟