31

我有一堂课:

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__在测试类中以某种方式模拟该方法,但我没有在文档中找到任何似乎有用的东西。

4

6 回答 6

52

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

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

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

于 2013-07-30T14:42:37.290 回答
7

您应该使用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

于 2019-06-21T16:58:30.550 回答
3

方法一:子类

请参考@Martijn Pieters 的回答。

方法2:控制反转

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

这种技术减少了依赖性并为您提供了更多的灵活性,例如您可以设置一个连接池并DatabaseThing从池中提供每个新的连接实例,而无需更改DatabaseThing.

于 2013-07-30T16:14:02.777 回答
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)      

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

于 2013-07-24T15:11:40.267 回答
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

嘲讽的例子:

模块A.py

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

模块B.py

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

我的类.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')
于 2013-07-27T20:45:50.740 回答
1

如果你想在初始化一个类时返回一个模拟,模拟出 __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)
于 2021-10-06T03:02:12.027 回答